Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sumrender/05bfcb3c137de65d633417a9049de3cf to your computer and use it in GitHub Desktop.
Save sumrender/05bfcb3c137de65d633417a9049de3cf to your computer and use it in GitHub Desktop.
understanding-dotnet-access-modifiers

.NET Assemblies and Access Modifiers - Hands-On Lab

This guide will help you understand .NET assemblies and access modifiers by building a multi-project solution from scratch.

What You'll Learn

  • How assemblies work in .NET
  • Difference between public, internal, and private access modifiers
  • How to reference assemblies and use their public APIs
  • Why internal class Program is used in modern .NET

Prerequisites

  • .NET 6 or later installed
  • Command line or terminal access

Step-by-Step Implementation

Step 1: Create Solution Structure

# Create a new directory for our solution
mkdir AssemblyDemo
cd AssemblyDemo

# Create a solution file
dotnet new sln -n AssemblyDemo

Step 2: Create the Main Console Application

# Create the main console app
dotnet new console -n MyApp
cd MyApp

# Look at the generated Program.cs - notice it uses 'internal class Program'
cat Program.cs
cd ..

# Add the project to solution
dotnet sln add MyApp/MyApp.csproj

Step 3: Create a Class Library

# Create a class library
dotnet new classlib -n MyLibrary
cd MyLibrary

# Remove the default Class1.cs
rm Class1.cs
cd ..

# Add the library to solution
dotnet sln add MyLibrary/MyLibrary.csproj

Step 4: Implement the Library Classes

Create MyLibrary/PublicUtility.cs:

namespace MyLibrary
{
    public class PublicUtility
    {
        public void PublicMethod()
        {
            Console.WriteLine("πŸ“’ This is a PUBLIC method from MyLibrary assembly");
            
            // This works - calling internal method within same assembly
            InternalMethod();
        }
        
        internal void InternalMethod()
        {
            Console.WriteLine("πŸ”’ This is an INTERNAL method (only visible within MyLibrary)");
        }
        
        private void PrivateMethod()
        {
            Console.WriteLine("🚫 This is a PRIVATE method (only visible within this class)");
        }
    }
}

Create MyLibrary/InternalUtility.cs:

namespace MyLibrary
{
    internal class InternalUtility
    {
        public void SomeMethod()
        {
            Console.WriteLine("πŸ”’ This is from an INTERNAL class - not accessible outside assembly");
        }
    }
}

Create MyLibrary/DatabaseHelper.cs:

namespace MyLibrary
{
    public class DatabaseHelper
    {
        public string ConnectionString { get; set; } = "DefaultConnection";
        
        public void Connect()
        {
            Console.WriteLine($"πŸ”— Connecting to database: {ConnectionString}");
        }
        
        internal void InternalCleanup()
        {
            Console.WriteLine("🧹 Internal cleanup method");
        }
    }
}

Step 5: Add Reference from MyApp to MyLibrary

# Add project reference
dotnet add MyApp/MyApp.csproj reference MyLibrary/MyLibrary.csproj

Step 6: Update the Main Application

Replace MyApp/Program.cs with:

using MyLibrary;

internal class Program  // Notice: internal - not accessible from other assemblies
{
    static void Main(string[] args)
    {
        Console.WriteLine("=== Assembly Demo ===");
        Console.WriteLine("MyApp.exe assembly can use PUBLIC classes from MyLibrary.dll");
        Console.WriteLine();
        
        // βœ… This works - PublicUtility is public
        var utility = new PublicUtility();
        utility.PublicMethod();
        
        // ❌ This would NOT compile - InternalMethod is internal to MyLibrary
        // utility.InternalMethod();  // Uncomment to see compilation error
        
        Console.WriteLine();
        
        // βœ… This works - DatabaseHelper is public
        var dbHelper = new DatabaseHelper();
        dbHelper.ConnectionString = "Server=localhost;Database=TestDB";
        dbHelper.Connect();
        
        // ❌ This would NOT compile - InternalCleanup is internal
        // dbHelper.InternalCleanup();  // Uncomment to see compilation error
        
        Console.WriteLine();
        
        // ❌ This would NOT compile - InternalUtility is internal to MyLibrary
        // var internalUtil = new InternalUtility();  // Uncomment to see compilation error
        
        Console.WriteLine("βœ… Demo completed successfully!");
    }
}

Step 7: Build and Run

# Build the entire solution
dotnet build

# Run the main application using dotnet
dotnet run --project MyApp

# OR run the executable directly (after building)
./MyApp/bin/Debug/net8.0/MyApp

Note: The build process creates:

  • MyApp - The main executable file
  • MyApp.dll - The managed assembly containing your code
  • MyLibrary.dll - The referenced library assembly (copied to output)

Step 8: Experiment with Access Modifiers

  1. Try breaking the rules:

    • Uncomment the commented lines in Program.cs
    • Try to build: dotnet build
    • Observe the compilation errors
  2. Check the generated assemblies:

    # After building, check the output
    ls MyApp/bin/Debug/net*/
    ls MyLibrary/bin/Debug/net*/
  3. Test assembly dependencies:

    # First, verify the app runs normally
    ./MyApp/bin/Debug/net8.0/MyApp
    
    # Now move the dependency and see what happens
    mv MyApp/bin/Debug/net8.0/MyLibrary.dll /tmp/
    ./MyApp/bin/Debug/net8.0/MyApp
    
    # You'll see an error like:
    # System.IO.FileNotFoundException: Could not load file or assembly 'MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
    
    # Restore the dependency
    mv /tmp/MyLibrary.dll MyApp/bin/Debug/net8.0/
    ./MyApp/bin/Debug/net8.0/MyApp  # Works again!
  4. Make InternalUtility public:

    • Change internal class InternalUtility to public class InternalUtility
    • Rebuild and see how this affects accessibility

Step 9: Understanding the Results

Expected Output:

=== Assembly Demo ===
MyApp.exe assembly can use PUBLIC classes from MyLibrary.dll

πŸ“’ This is a PUBLIC method from MyLibrary assembly
πŸ”’ This is an INTERNAL method (only visible within MyLibrary)

πŸ”— Connecting to database: Server=localhost;Database=TestDB

βœ… Demo completed successfully!

Key Takeaways

Assembly Boundaries

  • MyApp.exe = One assembly
  • MyLibrary.dll = Another assembly
  • Each has its own access control boundaries

Access Modifier Rules

Modifier Visibility
public Accessible from any assembly
internal Only accessible within the same assembly
private Only accessible within the same class

Why internal class Program?

  • Program class is an application entry point
  • No external assembly should instantiate it
  • Follows principle of least privilege
  • Modern .NET templates use this by default

Bonus Experiments

  1. Create a third project that references both MyApp and MyLibrary
  2. Try to access Program class from the new project (it won't work!)
  3. Create public static methods in Program class and see the difference
  4. Add XML documentation to understand what gets exposed in IntelliSense

Clean Up

# Remove the demo when done
cd ..
rm -rf AssemblyDemo

πŸŽ‰ Congratulations! You now understand how .NET assemblies and access modifiers work in practice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment