Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

Chapter 59: Generics

Parameter(s)

Description

T, V

Type placeholders for generic declarations

Section 59.1: Implicit type inference (methods)

When passing formal arguments to a generic method, relevant generic type arguments can usually be inferred implicitly. If all generic type can be inferred, then specifying them in the syntax is optional.

Consider the following generic method. It has one formal parameter and one generic type parameter. There is a very obvious relationship between them -- the type passed as an argument to the generic type parameter must be the same as the compile-time type of the argument passed to the formal parameter.

void M<T>(T obj)

{

}

These two calls are equivalent:

M<object>(new object());

M(new object());

These two calls are also equivalent:

M<string>("");

M("");

And so are these three calls:

M<object>("");

M((object) "");

M("" as object);

Notice that if at least one type argument cannot be inferred, then all of them have to be specified.

Consider the following generic method. The first generic type argument is the same as the type of the formal argument. But there is no such relationship for the second generic type argument. Therefore, the compiler has no way of inferring the second generic type argument in any call to this method.

void X<T1, T2>(T1 obj)

{

}

This doesn't work anymore:

X("");

This doesn't work either, because the compiler isn't sure if we are specifying the first or the second generic parameter (both would be valid as object):

X<object>("");

GoalKicker.com – C# Notes for Professionals

305

We are required to type out both of them, like this:

X<string, object>("");

Section 59.2: Type inference (classes)

Developers can be caught out by the fact that type inference doesn't work for constructors:

class Tuple<T1,T2>

{

public Tuple(T1 value1, T2 value2)

{

}

}

var

x

=

new

Tuple(2, "two");

//

This

WON'T work...

var

y

=

new

Tuple<int, string>(2, "two"); //

even

though the explicit form will.

The first way of creating instance without explicitly specifying type parameters will cause compile time error which would say:

Using the generic type 'Tuple<T1, T2>' requires 2 type arguments

A common workaround is to add a helper method in a static class:

static class Tuple

{

public static Tuple<T1, T2> Create<T1, T2>(T1 value1, T2 value2)

{

return new Tuple<T1, T2>(value1, value2);

}

}

var x = Tuple.Create(2, "two"); // This WILL work...

Section 59.3: Using generic method with an interface as a constraint type

This is an example of how to use the generic type TFood inside Eat method on the class Animal

public interface IFood

{

void EatenBy(Animal animal);

}

public class Grass: IFood

{

public void EatenBy(Animal animal)

{

Console.WriteLine("Grass was eaten by: {0}", animal.Name);

}

}

public class Animal

{

public string Name { get; set; }

GoalKicker.com – C# Notes for Professionals

306

public void Eat<TFood>(TFood food) where TFood : IFood

{

food.EatenBy(this);

}

}

public class Carnivore : Animal

{

public Carnivore()

{

Name = "Carnivore";

}

}

public class Herbivore : Animal, IFood

{

public Herbivore()

{

Name = "Herbivore";

}

public void EatenBy(Animal animal)

{

Console.WriteLine("Herbivore was eaten by: {0}", animal.Name);

}

}

You can call the Eat method like this:

var grass = new Grass();

var sheep = new Herbivore(); var lion = new Carnivore();

sheep.Eat(grass);

//Output: Grass was eaten by: Herbivore

lion.Eat(sheep);

//Output: Herbivore was eaten by: Carnivore

In this case if you try to call:

sheep.Eat(lion);

It won't be possible because the object lion does not implement the interface IFood. Attempting to make the above call will generate a compiler error: "The type 'Carnivore' cannot be used as type parameter 'TFood' in the generic type or method 'Animal.Eat(TFood)'. There is no implicit reference conversion from 'Carnivore' to 'IFood'."

Section 59.4: Type constraints (new-keyword)

By using the new() constraint, it is possible to enforce type parameters to define an empty (default) constructor.

class Foo

{

public Foo () { }

}

class Bar

{

GoalKicker.com – C# Notes for Professionals

307

public Bar (string s) { ... }

}

class Factory<T> where T : new()

{

public T Create()

{

return new T();

}

}

Foo f = new Factory<Foo>().Create(); // Valid.

Bar b = new Factory<Bar>().Create(); // Invalid, Bar does not define a default/empty constructor.

The second call to to Create() will give compile time error with following message:

'Bar' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'Factory'

There is no constraint for a constructor with parameters, only parameterless constructors are supported.

Section 59.5: Type constraints (classes and interfaces)

Type constraints are able to force a type parameter to implement a certain interface or class.

interface IType; interface IAnotherType;

//T must be a subtype of IType interface IGeneric<T>

where T : IType

{

}

//T must be a subtype of IType class Generic<T>

where T : IType

{

}

class NonGeneric

{

// T must be a subtype of IType public void DoSomething<T>(T arg)

where T : IType

{

}

}

//Valid definitions and expressions: class Type : IType { }

class Sub : IGeneric<Type> { } class Sub : Generic<Type> { }

new NonGeneric().DoSomething(new Type());

//Invalid definitions and expressions: class AnotherType : IAnotherType { }

GoalKicker.com – C# Notes for Professionals

308