#1,064 – Getting Around Inability to Explicitly Convert Type Parameters

You can’t explicitly convert a type parameter to either a value type or a reference type (you can convert to an interface).

To get around this restriction, you can use the as operator on the type parameter.  This gives you a way to effectively convert the type parameter, get the code to compile, and avoid runtime exceptions.

    class Program
    {
        public class ThingContainer<T>
        {
            private T thing;

            public void SetThing(T t)
            {
                thing = t;

                // Won't compile
                //int i = (int)t;

                // Do this instead
                int? i = t as int?;
                if (i.HasValue)
                    Console.WriteLine("Your int: " + i);

                // Won't compile
                //Dog d = (Dog)t;

                // Do this instead
                Dog d = t as Dog;
                if (d != null)
                    Console.WriteLine("Your Dog: " + d.Name);
            }
        }

        static void Main(string[] args)
        {
            ThingContainer<int> intcont = new ThingContainer<int>();
            intcont.SetThing(5);

            ThingContainer<Dog> dogcont = new ThingContainer<Dog>();
            dogcont.SetThing(new Dog("Bowser"));

            ThingContainer<Cow> cowcont = new ThingContainer<Cow>();
            cowcont.SetThing(new Cow("Bessie"));

            Console.ReadLine();
        }
    }

1064-001

Advertisement

#1,035 – Summary of Type Parameter Constraints

Here’s a quick summary of the different types of constraints that you can apply to type parameters:

#1,027 – Type Parameters vs. Type Arguments in a Generic Class

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.

Type parameters are used in the definition of the type.  In the code below, T1 and T2 are type parameters.

    public class ThingContainer<T1, T2>
    {
        private T1 thing1;
        private T2 thing2;

        public void SetThings(T1 first, T2 second)
        {
            thing1 = first;
            thing2 = second;
        }

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

You provide a type argument for each type parameter when you declare an instance of the generic type, constructing the type.  In the sample code below, string and int are the type arguments that map to the T1 and T2 type parameters.

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

#548 – No More Than One Class for a Type Parameter Constraint

When specifying constraints for a type parameter in a generic class, you can specify at most one class as a constraint, but  you can specify one or more interfaces.  If one of the constraints is a class type, it must come first.

// Type must be castable to DogToy and must implement
//   IEdible and IBuryable
public class Dog<TFavToy>
    where TFavToy : DogToy, IEdible, IBuryable

#547 – Things That Can Serve as Type Parameter Constraints

A constraint on a type parameter is often a base class or interface, but can actually take on a number of different forms.

A constraint can be a class type:

    // Type parameter can be some subclass of DogToy,
    // e.g. SqueakyToy, RopeToy
    public class Dog
        where TFavThing: DogToy

Or an interface type:

    public class Dog
        where TFavThing: IBuryable

Or another type parameter:

    // TFavThing must implement IBuryable
    // TOtherThing must be castable to TFavThing's type
    public class Dog<TFavThing,TOtherThing>
        where TFavThing: IBuryable
        where TOtherThing: TFavThing

class indicates that the type must be a reference type.

    public class Dog<TFavThing>
        where TFavThing: class

struct indicates that the type must be a non-nullable value type.

    public class Dog<TFavThing>
        where TFavThing: struct

new() indicates that the type must have a public parameterless constructor defined.

public class Dog<TFavThing>
    where TFavThing: new()

#545 – Specifying Constraints for More than One Type Parameter

When specifying constraints for type parameters in a generic class, you can specify constraints for more than one parameter.

To specify more than one constraint, just place each constraint on a separate line, with its own where clause.

    // TFavThing type parameter must be a type that implements IBuryable
    // TFavFood type parameter must be a type that implements IEdible
    public class Dog<TFavThing,TFavFood>
        where TFavThing: IBuryable
        where TFavFood: IEdible
    {
        public void BuryThing(TFavThing thing)
        {
            thing.Bury();
        }

        public void Eat(TFavFood eatThis)
        {
            eatThis.Eat();
        }
    }

When constructing this type, we just need to use types that implement the specified interfaces.

            // Bone implements IBuryable
            // RawFood implements IEdible
            Dog<Bone,RawFood> d = new Dog<Bone,RawFood>("Buster", 5);

            d.BuryThing(new Bone("Rawhide"));
            d.Eat(new RawFood(16));  // 16 oz chunk

#542 – Conventions for Naming Type Parameters

You can name type parameters in generic classes and generic methods anything you like.  But you should typically follow the following naming conventions for naming type parameters:

  • Use a short descriptive name
  • Use “T” as the first letter of the type parameter
  • Use “T” alone as the type parameter if it is the only parameter and if a longer name would not make its use more clear

Here are a couple examples from the .NET Framework source code:

    class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        IDictionary<TKey, TValue> dictionary;

        public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
            : this(dictionary, true)
        {

 

        public static ReadOnlyCollection<T> AsReadOnly<T>(T[] array) {