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

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

new NonGeneric().DoSomething(new AnotherType());

Syntax for multiple constraints:

class Generic<T, T1> where T : IType

where T1 : Base, new()

{

}

Type constraints works in the same way as inheritance, in that it is possible to specify multiple interfaces as constraints on the generic type, but only one class:

class A { /* ... */ } class B { /* ... */ }

interface I1 { } interface I2 { }

class Generic<T>

where T : A, I1, I2

{

}

class Generic2<T>

where T : A, B //Compilation error

{

}

Another rule is that the class must be added as the first constraint and then the interfaces:

class Generic<T> where T : A, I1

{

}

class Generic2<T>

where T : I1, A //Compilation error

{

}

All declared constraints must be satisfied simultaneously for a particular generic instantiation to work. There is no way to specify two or more alternative sets of constraints.

Section 59.6: Checking equality of generic values

If logic of generic class or method requires checking equality of values having generic type, use

EqualityComparer<TType>.Default property:

public void Foo<TBar>(TBar arg1, TBar arg2)

{

var comparer = EqualityComparer<TBar>.Default; if (comparer.Equals(arg1,arg2)

{

...

}

GoalKicker.com – C# Notes for Professionals

309

}

This approach is better than simply calling Object.Equals() method, because default comparer implementation checks, whether TBar type implements IEquatale<TBar> interface and if yes, calls IEquatable<TBar>.Equals(TBar other) method. This allows to avoid boxing/unboxing of value types.

Section 59.7: Reflecting on type parameters

The typeof operator works on type parameters.

class NameGetter<T>

{

public string GetTypeName()

{

return typeof(T).Name;

}

}

Section 59.8: Covariance

When is an IEnumerable<T> a subtype of a di erent IEnumerable<T1>? When T is a subtype of T1. IEnumerable is covariant in its T parameter, which means that IEnumerable's subtype relationship goes in the same direction as T's.

class Animal { /* ... */ } class Dog : Animal { /* ... */ }

IEnumerable<Dog> dogs = Enumerable.Empty<Dog>();

IEnumerable<Animal> animals = dogs; // IEnumerable<Dog> is a subtype of IEnumerable<Animal>

// dogs = animals; // Compilation error - IEnumerable<Animal> is not a subtype of IEnumerable<Dog>

An instance of a covariant generic type with a given type parameter is implicitly convertible to the same generic type with a less derived type parameter.

This relationship holds because IEnumerable produces Ts but doesn't consume them. An object that produces Dogs can be used as if it produces Animals.

Covariant type parameters are declared using the out keyword, because the parameter must be used only as an output.

interface IEnumerable<out T> { /* ... */ }

A type parameter declared as covariant may not appear as an input.

interface Bad<out T>

{

void SetT(T t); // type error

}

Here's a complete example:

using NUnit.Framework;

namespace ToyStore

{

enum Taste { Bitter, Sweet };

GoalKicker.com – C# Notes for Professionals

310

interface IWidget

{

int Weight { get; }

}

interface IFactory<out TWidget> where TWidget : IWidget

{

TWidget Create();

}

class Toy : IWidget

{

public int Weight { get; set; } public Taste Taste { get; set; }

}

class ToyFactory : IFactory<Toy>

{

public const int StandardWeight = 100;

public const Taste StandardTaste = Taste.Sweet;

public Toy Create() { return new Toy { Weight = StandardWeight, Taste = StandardTaste }; }

}

[TestFixture]

public class GivenAToyFactory

{

[Test]

public static void WhenUsingToyFactoryToMakeWidgets()

{

var toyFactory = new ToyFactory();

//// Without out keyword, note the verbose explicit cast:

//IFactory<IWidget> rustBeltFactory = (IFactory<IWidget>)toyFactory;

//covariance: concrete being assigned to abstract (shiny and new)

IFactory<IWidget> widgetFactory = toyFactory; IWidget anotherToy = widgetFactory.Create();

Assert.That(anotherToy.Weight, Is.EqualTo(ToyFactory.StandardWeight)); // abstract

contract

Assert.That(((Toy)anotherToy).Taste, Is.EqualTo(ToyFactory.StandardTaste)); // concrete

contract

}

}

}

Section 59.9: Contravariance

When is an IComparer<T> a subtype of a di erent IComparer<T1>? When T1 is a subtype of T. IComparer is

contravariant in its T parameter, which means that IComparer's subtype relationship goes in the opposite direction as T's.

class Animal { /* ... */ } class Dog : Animal { /* ... */ }

IComparer<Animal> animalComparer = /* ... */;

IComparer<Dog> dogComparer = animalComparer; // IComparer<Animal> is a subtype of IComparer<Dog> // animalComparer = dogComparer; // Compilation error - IComparer<Dog> is not a subtype of IComparer<Animal>

GoalKicker.com – C# Notes for Professionals

311

An instance of a contravariant generic type with a given type parameter is implicitly convertible to the same generic type with a more derived type parameter.

This relationship holds because IComparer consumes Ts but doesn't produce them. An object which can compare any two Animals can be used to compare two Dogs.

Contravariant type parameters are declared using the in keyword, because the parameter must be used only as an input.

interface IComparer<in T> { /* ... */ }

A type parameter declared as contravariant may not appear as an output.

interface Bad<in T>

{

T GetT(); // type error

}

Section 59.10: Invariance

IList<T> is never a subtype of a di erent IList<T1>. IList is invariant in its type parameter.

class Animal { /* ... */ } class Dog : Animal { /* ... */ }

IList<Dog> dogs = new List<Dog>();

IList<Animal> animals = dogs; // type error

There is no subtype relationship for lists because you can put values into a list and take values out of a list.

If IList was covariant, you'd be able to add items of the wrong subtype to a given list.

IList<Animal> animals = new List<Dog>(); // supposing this were allowed...

animals.Add(new Giraffe()); // ... then this would also be allowed, which is bad!

If IList was contravariant, you'd be able to extract values of the wrong subtype from a given list.

IList<Dog> dogs = new List<Animal> { new Dog(), new Giraffe() }; // if this were allowed...

Dog dog = dogs[1]; // ... then this would be allowed, which is bad!

Invariant type parameters are declared by omitting both the in and out keywords.

interface IList<T> { /* ... */ }

Section 59.11: Variant interfaces

Interfaces may have variant type parameters.

interface IEnumerable<out T>

{

// ...

}

interface IComparer<in T>

{

// ...

GoalKicker.com – C# Notes for Professionals

312