#541 – Generic Method Type Parameters Can Hide Class-Level Type Parameters

When you have generic methods within a generic class, if the name of a type parameter in the generic method matches the name of a type parameter for the class, the parameter in the method takes precedence.

In the example below, both the Dog class and the BuryThing method declare a type parameter named T.  Within the body of the BuryThing method, the method’s type parameter is used, rather than the class-level type parameter of the same name.

    public class Dog<T>
    {
        // This method's type parameter hides the class-level type parameter
        public void BuryThing<T>(T thing)
        {
            Console.WriteLine("T's type is {0}", typeof(T).ToString());
        }

 

            Dog<Cow> d = new Dog<Cow>("Buster", 5);
            d.BuryThing(new Bone("Rawhide"));

To avoid this confusion, you should give your type parameters meaningful names, e.g. TDogThing and TThingToBury.

#540 – Non-Generic Methods in a Generic Class

You can have both generic and non-generic methods in a generic class.  The non-generic methods can still make use of the class’ generic type parameters.

In the example below, the BuryThing method accepts a parameter whose type matches the type parameter of the class itself.

    public class Dog<T>
    {
        // Not classified as generic method because it does not introduce new type parameter
        public void BuryThing(T thing)
        {
            Console.WriteLine(string.Format("{0} is burying: {1}", Name, thing.ToString()));
        }

When we invoke this method, we must pass it an object whose type matches the type parameter that the class was constructed with.

            Dog<Cow> d = new Dog<Cow>("Buster", 5);
            d.BuryThing(new Cow("Bessie"));

#539 – Type Inference When Calling Generic Methods

When you call a generic method, you can provide a type argument, indicating the type for any type parameters, or you can let the type be inferred by the compiler.

For the following type definition:

public static void DogBuriesThing<T>(Dog d, T thing)

You can either provide the type argument:

            Dog buster = new Dog("Buster", 5);
            Dog.DogBuriesThing<Bone>(buster, new Bone("Buster's rawhide"));

Or you can omit the type when you call the method, letting the type be inferred based on the type of the second argument:

            Dog.DogBuriesThing(buster, new Cow("Bessie"));

#538 – Implement a Generic Method

In addition to classes, structs and interfaces, a method can also be generic.  A generic method is one that is declared with type parameters.  A generic method can be either a static method or an instance method.

In the example below, notice that both methods are generic, though the Dog class itself is not generic.

    public class Dog
    {
        // Generic static method
        public static void DogBuriesThing<T>(Dog d, T thing)
        {
            Console.WriteLine(string.Format("{0} is burying: {1}", d.Name, thing.ToString()));
        }

        // Generic instance method
        public void BuryThing<T>(T thing)
        {
            Console.WriteLine(string.Format("{0} is burying: {1}", Name, thing.ToString()));
        }

Examples of calling these methods:

            // Call generic static method
            Dog buster = new Dog("Buster", 5);
            Dog.DogBuriesThing<Bone>(buster, new Bone("Buster's rawhide"));

            // Call generic instance method
            buster.BuryThing<Cow>(new Cow("Bessie"));

#537 – Implement a Generic Interface with a Generic Class

When a class makes use of a generic interface, it can choose to implement the constructed interface, supplying all type parameters for the interface.

public class Farmer : IRememberMostRecent<Joke>

A class can also implement a generic interface, as long as the class itself is generic. Type parameters for the interface are supplied to the class when it is constructed.

    public class Dog<T> : ICanEat<T>

This type parameter (or parameters) can then be used in instance methods that implement the interface.

        // ICanEat.Eat
        public void Eat(T thingToEat)
        {
            // ...
        }

You can also use the type parameter passed to the class in instance methods that are not part of the interface.

        // Not part of the interface
        public void PlayWith(T thingToPlayWith)
        {
            // ...
        }

#536 – Using a Generic Interface

Like classes, interfaces can be generic.  Below is an example of a generic interface with  a single type parameter.

    public interface IRememberMostRecent<T>
    {
        void Remember(T thingToRemember);
        T TellMeMostRecent();
        List<T> PastThings { get; }
    }

When a class implements this interface, it can choose to fully construct the interface (provide a type).

    public class Farmer : IRememberMostRecent<Joke>
    {
        public string Name { get; protected set;  }

        public Farmer(string name)
        {
            Name = name;
            lastJoke = null;
            allJokes = new List<Joke>();
        }

        // IRememberMostRecent implementation
        private Joke lastJoke;
        private List<Joke> allJokes;

        public void Remember(Joke jokeToRemember)
        {
            if (lastJoke != null)
                allJokes.Add(lastJoke);

            lastJoke = jokeToRemember;
        }

        public Joke TellMeMostRecent()
        {
            return lastJoke;
        }

        public List<Joke> PastThings
        {
            get { return allJokes; }
        }
    }

Using the Farmer class:

            Farmer burton = new Farmer("Burton");
            burton.Remember(new Joke("A man walks into a bar.", "Ouch"));
            burton.TellMeMostRecent().Output();

            burton.Remember(new Joke("What's red and invisible?", "No tomatoes"));
            burton.TellMeMostRecent().Output();

#535 – Creating a Generic Struct

In addition to generic classes, you can also create a generic struct.  Like a class, the generic struct definition serves as a sort of template for a strongly-typed struct.  When you declare a variable of this struct type, you provide a type for its generic parameter.

    public struct ThreeTuple<T>
    {
        public ThreeTuple(T x, T y, T z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public T X;
        public T Y;
        public T Z;
    }

    public class Program
    {
        static void Main()
        {
            ThreeTuple<int> intTuple = new ThreeTuple<int>(32, 10, 12);
            int yVal = intTuple.Y;

            ThreeTuple<double> dblTuple = new ThreeTuple<double>(1.2, 3.4, 5.6);
            double yVal2 = dblTuple.Y;
        }
   }

#534 – What Good Are Generics?

generic class takes one or more type parameters, serving as a sort of template for an actual class.

Assume you want to implement a Stack type and that we want to create several kinds of stacks–e.g. a stack of Movie objects, a stack of integers, etc.

Without generics, we could create a new class for each data item, e.g. MovieStackIntStack.  But this would result in a lot of duplicated code.

We could create a stack of object instances, since everything inherits from object.  But this would allow mixing and matching objects of different types in the stack.  It could also lead to lower performance, as value types were boxed/unboxed when added/removed from the stack.

The compromise is to use generics, e.g. Stack<Movie> and Stack<int>.  Then at run-time, we really have two separate types (constructed types), each of which is strongly typed.  But we’ve shared all of the code.

#533 – Alternate Notation for Extern Aliases

You can use identically named types that come from different assemblies by setting up an extern alias and then using that alias to qualify the type names.

Once you’ve set up an extern alias, there are two different syntax variations that you can use in qualifying type names from an aliased assembly.  You can use either a colon (:) or a dot (.) after the alias.

    extern alias AnotherLib;

    class Program
    {
        static void Main()
        {
            // Syntax variant #1
            AnotherLib::DogLibrary.Dog d2 = new AnotherLib::DogLibrary.Dog("JRT", "Jack");

            // Syntax variant #2
            AnotherLib.DogLibrary.Dog d3 = new AnotherLib.DogLibrary.Dog("Rough Collie", "Lassie");
        }
    }

#532 – Using Identically Named Types from Different Assemblies

If you want to use two types from different assemblies that have exactly the same name, including the namespace, your application won’t know which type to use.  The fully qualified type names are identical and you’ll get an error.

You can use both types in your project by providing an extern alias for the namespace hierarchy in one of the DLLs.

In Visual Studio, in your application, select the reference to one of the two assemblies.  Change the Aliases property from global to a new name.

Now include an extern alias statement in your code, using the same name.  Then use this alias to reference types in the aliased assembly.

using DogLibrary;   // FirstDogLibrary.dll

namespace ConsoleApplication1
{
    extern alias AnotherLib;

    class Program
    {
        static void Main()
        {
            // DogLibrary.Dog in FirstDogLibrary.dll
            Dog d = new Dog("Kirby", 12);

            // DogLibrary.Dog in AnotherDogLibrary.dll
            AnotherLib::DogLibrary.Dog d2 = new AnotherLib::DogLibrary.Dog("JRT", "Jack");
        }
    }
}