#551 – Use Anonymous Method When Adding to an Invocation List

Recall that an instance of a delegate can refer to more than one method (delegates are multicast).  In addition to using an anonymous method to declare a new instance of a delegate, you can also use anonymous methods when adding a new method to an existing delegate instance.  (Adding to the delegate instance’s invocation list).

        private delegate void StringHandlerDelegate(string s);

        static void Main()
        {
            // Declare new delegate instance, with anonymous
            // method in its invocation list
            StringHandlerDelegate del1 =
                delegate (string s) { Console.WriteLine(s); };

            // Now add a 2nd method to delegate, also using
            // anonymous method
            del1 +=
                delegate(string s) { Console.WriteLine(new string(s.Reverse().ToArray())); };

            // When we invoke the delegate, both methods are
            // called
            del1("Mairzy Doats and Dozy Doats");
        }

Advertisement

#550 – Anonymous Method Does Not Require Formal Parameters

An anonymous method declares the body of code to execute for a delegate inline with the declaration of the delegate instance.

In the example below, the delegate type takes a single string parameter and we include this parameter in our declaration of the anonymous method.

        private delegate void StringHandlerDelegate(string s);

        static void Main()
        {
            StringHandlerDelegate del1 =
                delegate (string s) { Console.WriteLine(s); };
            del1("Invoked via delegate");

The anonymous method expression shown above includes a list of format parameters, in this case a single string parameter. However, this list of parameters is optional, even if the delegate type includes one or more parameters. In the example below, we define a second anonymous method for the same delegate type, but without any formal parameters.

            StringHandlerDelegate del2 =
                delegate { Console.WriteLine("I'm ignoring my string parameter!"); };
            del2("Ignore me");

#549 – Anonymous Method Basics

Recall that an instance of a delegate is an object that refers to one or more methods.  Code that has access to the delegate  instance can execute the various methods via the delegate.

        private delegate void StringHandlerDelegate(string s);

        static void Main()
        {
            StringHandlerDelegate del1 = Method1;
            del1("Invoked via delegate");
        }

        static void Method1(string text)
        {
            Console.WriteLine(text);
        }

An anonymous method is a shorthand for declaring a delegate instance that allows declaring the code to execute as part of the delegate’s declaration, rather than in a separate method.

The code below is equivalent to the example above, but using an anonymous method, rather than a separately defined method.

        private delegate void StringHandlerDelegate(string s);

        static void Main()
        {
            StringHandlerDelegate del1 =
                delegate (string s) { Console.WriteLine(s); };
            del1("Invoked via delegate");
        }

#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()

#546 – Specifying More than One Constraint for the Same Type Parameter

You can enforce a constraint on a type parameter for a generic class using the where keyword.  Following the where keyword, you typically indicate a type or interface that the actual parameter must adhere to.

You can define more than one constraint for the same type parameter.

In the example below, the TFavThing parameter must represent a type that implements both the IBuryable and IEdible interfaces.

    public class Dog<TFavThing>
        where TFavThing: IBuryable, IEdible
    {
        public void BuryThing(TFavThing thing)
        {
            thing.Bury();
        }

        public void Eat(TFavThing eatThis)
        {
            eatThis.Eat();
        }
            Dog<Bone> d = new Dog<Bone>("Buster", 5);

            Bone myBone = new Bone("Rawhide");
            d.BuryThing(myBone);
            d.Eat(myBone);

#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

#544 – Specifying a Constraint for a Type Parameter

When you provide a type parameter to a generic class, you can only invoke methods in System.Object for objects of that type.

    public class Dog<TFavThing>
    {
        public void BuryThing(TFavThing thing)
        {
            // Err: TFavThing does not contain a definition for Bury
            thing.Bury();
        }

You can allow calling methods in a specific type by adding a constraint to the type parameter, indicating what type the parameter must conform to.  Adding a constraint lets you do two things:

  • Ensure that a type parameter belongs to a specific type
  • Allow you to call methods in a specific type, for objects whose type matches the type parameter

A constraint consists of a type parameter name following by an indication of the type that it must be.  (Generally a base class or an interface).

    // TFavThing type parameter must be a type that implements IBuryable
    public class Dog<TFavThing> where TFavThing: IBuryable

#543 – Operations You Can Perform on a Generically Typed Object

When you implement a generic class, it accepts one or more type parameters.  The idea of generics is that code in your class can act upon objects whose type matches the type of these parameters.  But their type is only fully determined when the generic class is constructed, that is–when you create an instance of the class and supply concrete types for the type parameters.

This means that the code within your class doesn’t know the actual type of your type parameters. So how can you write code that uses objects of this type?

At compile time, the compiler will assume that objects whose type is one of the type parameters are of type object.  This means that you’re limited to methods defined in System.Object.

In the example below, the list of methods displayed by Intellisense is limited to those appropriate for the object type.

#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) {