In C# we have 5 types of scope, the first three are common and simple to understand:
- public: accessible anywhere any time.
- private: accessible within the the declared class.
- protected: accessible within the the declared or any derived class.
The last two are not as clear nor as common.
- internal: accessible within the current assembly
- protected internal: accessible within the current assembly or from a derived class, even if that derived class is in a different assembly.
Before we can talk about the internal and protected internal scope we need to be clear on what an assembly is. An assembly by default can be though of as your visual studio project, not the solution, that can be made up of one or more projects which are compiled into there own assemblies.
One caveat is that you can compile multiple projects into one assembly using the
Assembly linker tool.
To demonstrate internal and protected internal scope, we are going to have to create two dotnet core projects and link them together.
In your command prompt, let's start by creating a root folder for our two projects, and navigate into it
mkdir pav.root
cd pav.root
now inside of pav.root let's create two dotnet core project, a Console app and a Linked library.
dotnet new console -n pav.console --use-program-main
dotnet new classlib -n pav.library
with our projects created, let's open up our pav.root folder in code, in the pav.root folder enter in
code .
finally with our root folder open, we are going to have to reference our pav.library project in our pav.console project.
dotnet add e:\learn\pav.root\pav.console\pav.console.csproj reference e:\learn\pav.root\pav.library\pav.library.csproj
your terminal should look similar to the following
Just to make sure that our classes are linked together, open the csproj file of your console app.
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\pav.library\pav.library.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
notice the ProjecctReference node in the ItemGroup.
With our two projects created, let's add two classes to the library project; Person and Employee
namespace pav.library;
public class Person : IComparable<Person>
{
// can be access from anywhere
public string Name { get; set; }
// can only be accessed within this assembly, only inside our library
protected DateTime BirthDate { get; set; }
// can accessed from anywhere
public Person(string Name, DateTime BirthDate)
{
this.Name = Name;
this.BirthDate = BirthDate;
}
// can only be accessed within this assembly, only inside our library
internal int GetAge()
{
DateTime today = DateTime.Today;
int age = today.Year - BirthDate.Year;
return BirthDate > today.AddYears(-age) ? --age : age;
}
// can be accessed within this assembly, or from any dereived type outside our assembly
protected internal virtual string Display() { return $"{Name} is {GetAge()}"; }
public int CompareTo(Person? other)
{
if (other != null)
return GetAge() - other.GetAge();
return 0;
}
}
public class Employee : Person
{
static int _runningID;
public int Id { get; private set; } = _runningID++;
public Employee(string Name, DateTime BirthDate) : base(Name, BirthDate) { }
protected internal override string Display() { return $"{base.Display()} with id:{Id}"; }
}
In the above, in our base Person class we have our Birthdate property marked as protected, meaning that no other class outside of our library assembly will have access to it, however our Display function is marked as protected internal, meaning that if we derive from Person or Employee in a different assembly, then we'll have access to it.
Let's try it out, let's instantiate instances of our person and employee classes in our console assembly and see what properties and functions we have access to, keep in mind that we need to add the using statement for our library in our console assembly.
using pav.library;
namespace pav.console;
class Program
{
static void Main(string[] args)
{
var pav = new Person("pav", new DateTime(1984, 1, 31));
var tom = new Employee("tom", new DateTime(1984, 1, 31));
Console.WriteLine(pav.Name);
Console.WriteLine(tom.Name);
}
}
as you can see from our Intellesence bellow, we only have access to our public properties and functions on the instance of our classes from library assembly.
Let's create a Student class inside our console assembly that derives from the Person class in our library assembly.
using pav.library;
namespace pav.console;
class Student : Person
{
public int StudentId { get; set; }
public Student(string Name, DateTime BirthDate, int studentId) : base(Name, BirthDate){
this.StudentId = studentId;
}
public string DisplayFromBase() =>
$"{base.Display()} years old with a student Id of {this.StudentId}";
}
class Program
{
static void Main(string[] args)
{
var pav = new Person("pav", new DateTime(1984, 1, 31));
var tom = new Employee("tom", new DateTime(1988, 8, 31));
Console.WriteLine(pav.Name);
Console.WriteLine(tom.Name);
var bob = new Student("bob", new DateTime(1995, 4, 15), 101388431);
Console.WriteLine(bob.DisplayFromBase());
}
}
in the above we created a Student Class that inherits from person, in our new Student class we created a functions called DisplayFromBase, in this function we call Person's implementation of the Display() function which has the scope of protected internal, meaning that it's not accessible on instances of Person, however it is available to classes that inherit from person.
Also notice that, in our Student class we do not direct access to the getAge() function, because it is scoped as simply internal, meaning that no access outside of our library assembly is available.
fun fact fields and functions are private by default, while properties are internal. meaning that fields or functions can only be accessed with in their class scope by default, whereas properties can only be access within their assembly.