#325 – Intellisense Understands Generic Classes

When using a generic class, Intellisense understands the type parameters passed to the class when it was constructed.  It will display the correct types when showing information for any method or property of the generic class.

Suppose we have a generic class defined as follows:

    public class ThingContainer<TThing1, TThing2>
    {
        private TThing1 thing1;
        private TThing2 thing2;

        public void SetThings(TThing1 first, TThing2 second)
        {
            thing1 = first;
            thing2 = second;
        }
    }

And suppose that we use the class as follows:

            ThingContainer<Dog, DateTime> container = new ThingContainer<Dog, DateTime>();

Now when we start entering the name of the ThingContainer.SetThings method, Intellisense displays the correct types for the method’s parameters.

#324 – A Generic Class Can Have More than One Type Parameter

A generic class includes one or more type parameters that will be substituted with actual types when the class is used.  If the class has more than one type parameter, all parameters must be substituted when the class is used.

Here’s an example of a generic class that stores two objects, each having its own type.

    public class ThingContainer<TThing1, TThing2>
    {
        private TThing1 thing1;
        private TThing2 thing2;

        public void SetThings(TThing1 first, TThing2 second)
        {
            thing1 = first;
            thing2 = second;
        }

        public string DumpThings()
        {
            return string.Format("{0}, {1}", thing1.ToString(), thing2.ToString());
        }
    }

We can use this class as follows:

            ThingContainer<string,int> cont1 = new ThingContainer<string,int>();
            cont1.SetThings("Hemingway", 1899);
            Console.WriteLine(cont1.DumpThings());      //  Hemingway, 1899

            ThingContainer<Dog, DateTime> cont2 = new ThingContainer<Dog, DateTime>();
            cont2.SetThings(new Dog("Kirby", 14), new DateTime(1998, 5, 1));
            Console.WriteLine(cont2.DumpThings());      // Kirby (14 yrs), 5/1/1998 12:00:00 AM

#323 – A Generic Class is a Template for a Class

A generic class is a class that takes one or more type parameters, which it then uses in the definition of the class.  It can be thought of as a template for a class.

    public class ThingContainer<TParam>
    {
        private TParam theThing;

        public void SetThing(TParam newValue)
        {
            theThing = newValue;
        }
    }

You use a generic class by specifying a type for each of the type parameters.

            ThingContainer<int> intContainer = new ThingContainer<int>();
            intContainer.SetThing(5);

            ThingContainer<Dog> dogContainer = new ThingContainer<Dog>();
            dogContainer.SetThing(new Dog("Kirby", 5));

In this example, we use a generic class to store an object of an arbitrary type.  We use one version of the class to store an int and another to store a Dog.  Notice that wherever we use the name of the generic class to define an instance, we need to supply a typename (e.g. int, Dog) as a parameter.

#322 – Class Accessibility

The members of a class all have an associated access modifier, which defines their accessibility.  A class itself also has an accessibility level, which dictates which code can make use of the class.

The two types of accessibility for a class are:

  • public – all code can use the class
  • internal – only code in the same .exe or .dll can use the class

In the code below, the Dog class, defined in DogLibrary.dll, is marked as internal.  This means that only code within the same DLL (highlighted blue) can create and use instances of the Dog class.

The Program class in Program.exe has access to the DogKennel class, but not the Dog class.  It can create an instance of a DogKennel, but not an instance of a Dog.

    class Program
    {
        static void Main()
        {
            // Ok
            DogKennel k = new DogKennel();
            k.EverybodyBark();

            // ERROR
            Dog d = new Dog("Kirby");

#321 – Accessibility of Constants

As with other class members, you can apply access modifiers to constants defined in a class to define their accessibility.  Accessibility dictates what other code has access to the constant’s value.

  • public – All code has access
  • private – Only code in the defining class has access
  • protected – Code in the defining class or derived classes has access
  • internal – All code in the defining assembly has access
  • protected internal – Code in the defining assembly or in derived classes has access
        // All code has access
        public const string PluralOfDog = "Dogs";

        // Only this class has access
        private const Breeds DefaultBreed = Breeds.Retriever;

        // Subclass has access
        protected const string DefaultDogName = "Bowser";

        // Code in same assembly has access
        internal const string TargetArchitecture = "x86";

        // Code in same assembly or subclass has access
        protected internal const int DeckSize = 52;

#320 – The Constant Expression for a Reference-Typed Constant Must Be Null

The declaration of a constant may use a reference type as the type of the constant.  However, reference types other than string are seldom seen.  The compiler needs to evaluate the value of the constant at compile time.  This means that the constant expression cannot invoke a method on a reference type.  The only valid value in the expression is therefore null.

            // Marginally useful
            const Dog TheNullDog = null;

            // Not allowed - Compile-time ERROR
            const Dog ConstantBob = new Dog("Bob");

One exception to this rule about only using null in a constant expression for a reference type is the string (System.String) class.  A constant of type string may be initialized with a string literal.

            // string constants can be initialized, although they are reference types
            const string MyDogsName = "Kirby";

#319 – You Initialize a Constant Using an Expression

You initialize a constant in C# using either a literal or an expression that resolves to the correct type.

    public class Dog
    {
        const string Demeanor = "friendly";
        const int NumberOfLegs = 4;
        const double OneThird = 1.0 / 3.0;

You can also use the value of another constant in a constant expression.

        const int NumberOfLegs = 4;
        const int NumberOfEyes = 2;
        const int EyeAndLegCount = NumberOfLegs + NumberOfEyes;

The constant expression must be able to be resolved at compile time, so you can’t use something that is not a constant.  This includes variables and also includes the results of method calls.

        static int NumLegs = 4;   // Not a constant

        // Error: The expression being assigned to 'ConsoleApplication2.Dog.LegsPlusOne' must be constant
        const int LegsPlusOne = NumLegs + 1;

#318 – You Can’t Use the static Modifier On a Constant

Although constants can be treated as static member variables, you cannot include the static keyword when declaring a constant.  A constant is always effectively static, so the static keyword would be redundant.  The compiler will generate an error if you include it.

        // This is ok
        const string Demeanor = "friendly";

        // !! Compiler error - constant cannot be marked static
        static const int TheAnswer = 42;

#317 – Constants Can Be Class Members

You can declare constants within a method or within a class.

If a constant is declared in a class, it’s treated  implicitly as a static member of the class–specifically, a static field.  Because the constant’s value can’t change, and was initialized when the constant was declared, it is effectively static because there is only a single value.

In declaring the constant at the class level, you do not use the static keyword.

    public class Dog
    {
        public const string Demeanor = "friendly";

Inside the class, you use the constant in the same way that you’d use a static field–referencing it by name.

        public void ShowDogInfo()
        {
            Console.WriteLine("Name: {0}", Name);
            Console.WriteLine("Demeanor: {0}", Demeanor);
        }

Outside of the class, you also use the constant like any other static class member, prefixing it with the name of the class.

            Console.WriteLine(Dog.Demeanor);

#316 – Declaring and Using Constants

A constant is a variable whose value does not change during the life of the application.

You declare a constant using the const keyword.  You must initialize a constant when it is declared, specifying a literal value of the appropriate type.

You can use a constant in the same way that you’d use a normal variable of the same type.

            const string DogName = "Kirby";
            const int NumBarks = 3;
            const char NameDelimiter = '-';
            const double FoodVolume = 1.2;

            Dog d = new Dog(DogName);
            d.Bark(NumBarks);
            Console.WriteLine("{0}{1}{2}", NameDelimiter, d.Name, NameDelimiter);
            d.FeedFoodInCups(FoodVolume);

The compiler will generate an error at compile-time if you try to change the value of a constant.