C# Language
Дженерики
Поиск…
Синтаксис
-
public void SomeMethod <T> () { }
-
public void SomeMethod<T, V>() { }
-
public T SomeMethod<T>(IEnumerable<T> sequence) { ... }
-
public void SomeMethod<T>() where T : new() { }
-
public void SomeMethod<T, V>() where T : new() where V : struct { }
-
public void SomeMethod<T>() where T: IDisposable { }
-
public void SomeMethod<T>() where T: Foo { }
-
public class MyClass<T> { public T Data {get; set; } }
параметры
Параметр (ы) | Описание |
---|---|
ТВ | Введите заполнители для общих деклараций |
замечания
Дженерики в C # поддерживаются вплоть до среды выполнения: общие типы, созданные с помощью C #, будут иметь свою обобщенную семантику, даже после компиляции в CIL .
Это фактически означает, что в C # можно отражать общие типы и видеть их по мере их объявления или проверять, является ли объект экземпляром общего типа, например. Это контрастирует с стиранием типа , когда информация о родовом типе удаляется во время компиляции. Это также контрастирует с шаблоном подход к дженерикам, где несколько конкретных родовых типов становятся множественными не-генерическими типами во время выполнения, и любые метаданные, необходимые для последующего создания исходных описаний общего типа, теряются.
Однако будьте осторожны при отражении на общих типах: имена обобщенных типов будут изменены при компиляции, заменяя угловые скобки и имена типов параметров обратным ходом, а затем число параметров типового типа. Таким образом, Dictionary<TKey, Tvalue>
будут переведены на Dictionary`2
.
Параметры типа (классы)
Декларация:
class MyGenericClass<T1, T2, T3, ...>
{
// Do something with the type parameters.
}
Инициализация:
var x = new MyGenericClass<int, char, bool>();
Использование (как тип параметра):
void AnotherMethod(MyGenericClass<float, byte, char> arg) { ... }
Параметры типа (методы)
Декларация:
void MyGenericMethod<T1, T2, T3>(T1 a, T2 b, T3 c)
{
// Do something with the type parameters.
}
Призвание:
Нет необходимости поставлять аргументы типа genry-методу, потому что компилятор может косвенно выводить тип.
int x =10;
int y =20;
string z = "test";
MyGenericMethod(x,y,z);
Однако, если есть двусмысленность, общие методы нужно вызывать с аргументами типа, как
MyGenericMethod<int, int, string>(x,y,z);
Параметры типа (интерфейсы)
Декларация:
interface IMyGenericInterface<T1, T2, T3, ...> { ... }
Использование (в наследовании):
class ClassA<T1, T2, T3> : IMyGenericInterface<T1, T2, T3> { ... }
class ClassB<T1, T2> : IMyGenericInterface<T1, T2, int> { ... }
class ClassC<T1> : IMyGenericInterface<T1, char, int> { ... }
class ClassD : IMyGenericInterface<bool, char, int> { ... }
Использование (как тип параметра):
void SomeMethod(IMyGenericInterface<int, char, bool> arg) { ... }
Неявный тип вывода (методы)
При передаче формальных аргументов в общий метод соответствующие аргументы общего типа обычно можно вывести неявно. Если все типичные типы можно вывести, то указание их в синтаксисе является необязательным.
Рассмотрим следующий общий метод. Он имеет один формальный параметр и один общий параметр типа. Между ними существует очень очевидная взаимосвязь: тип, переданный как аргумент параметру generic type, должен быть таким же, как тип времени компиляции аргумента, переданного формальному параметру.
void M<T>(T obj)
{
}
Эти два вызова эквивалентны:
M<object>(new object());
M(new object());
Эти два вызова также эквивалентны:
M<string>("");
M("");
И вот эти три звонка:
M<object>("");
M((object) "");
M("" as object);
Обратите внимание: если хотя бы один аргумент типа не может быть выведен, то все они должны быть указаны.
Рассмотрим следующий общий метод. Первый общий аргумент типа совпадает с типом формального аргумента. Но для второго аргумента типа типа нет таких отношений. Поэтому компилятор не может вывести второй аргумент типа общего типа при любом вызове этого метода.
void X<T1, T2>(T1 obj)
{
}
Это больше не работает:
X("");
Это тоже не работает, потому что компилятор не уверен, указываем ли мы первый или второй общий параметр (оба будут действительными как object
):
X<object>("");
Мы должны вывести оба из них, например:
X<string, object>("");
Ограничения типа (классы и интерфейсы)
Ограничения типа могут принудительно вводить параметр типа для реализации определенного интерфейса или класса.
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 { }
class Sub : IGeneric<AnotherType> { }
class Sub : Generic<AnotherType> { }
new NonGeneric().DoSomething(new AnotherType());
Синтаксис для нескольких ограничений:
class Generic<T, T1>
where T : IType
where T1 : Base, new()
{
}
Ограничения типа работают так же, как наследование, поскольку в качестве ограничений для общего типа можно указать несколько интерфейсов, но только один класс:
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
{
}
Другое правило состоит в том, что класс должен быть добавлен как первое ограничение, а затем интерфейсы:
class Generic<T>
where T : A, I1
{
}
class Generic2<T>
where T : I1, A //Compilation error
{
}
Все объявленные ограничения должны выполняться одновременно для конкретного типичного экземпляра для работы. Нет способа указать два или более альтернативных набора ограничений.
Ограничения типа (класс и структура)
Можно указать, должен ли тип-тип быть ссылочным типом или типом значения с использованием соответствующего class
ограничений или struct
. Если эти ограничения используются, они должны быть определены до того, как все остальные ограничения (например, родительский тип или new()
) могут быть перечислены.
// TRef must be a reference type, the use of Int32, Single, etc. is invalid.
// Interfaces are valid, as they are reference types
class AcceptsRefType<TRef>
where TRef : class
{
// TStruct must be a value type.
public void AcceptStruct<TStruct>()
where TStruct : struct
{
}
// If multiple constraints are used along with class/struct
// then the class or struct constraint MUST be specified first
public void Foo<TComparableClass>()
where TComparableClass : class, IComparable
{
}
}
Ограничения типа (новое ключевое слово)
Используя ограничение new()
, можно задать параметры типа для определения пустого (по умолчанию) конструктора.
class Foo
{
public Foo () { }
}
class Bar
{
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.
Второй вызов Create()
даст ошибку времени компиляции со следующим сообщением:
«Бар» должен быть не абстрактным типом с открытым конструктором без параметров, чтобы использовать его как параметр «Т» в родовом типе или методе «Фабрика»,
Для конструктора с параметрами нет ограничений, поддерживаются только конструкторы без параметров.
Тип вывода (классы)
Разработчики могут быть пойманы тем фактом, что вывод типа не работает для конструкторов:
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.
Первый способ создания экземпляра без явного указания параметров типа приведет к ошибке времени компиляции, которая скажет:
Использование типичного типа «Tuple <T1, T2>» требует 2 аргумента типа
Общим решением является добавление вспомогательного метода в статический класс:
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...
Отражение по параметрам типа
Оператор typeof
работает с параметрами типа.
class NameGetter<T>
{
public string GetTypeName()
{
return typeof(T).Name;
}
}
Явные параметры типа
Существуют разные случаи, когда необходимо явно указать параметры типа для общего метода. В обоих нижеприведенных случаях компилятор не может вывести все параметры типа из указанных параметров метода.
Один случай - когда нет параметров:
public void SomeMethod<T, V>()
{
// No code for simplicity
}
SomeMethod(); // doesn't compile
SomeMethod<int, bool>(); // compiles
Второй случай - когда один (или более) параметров типа не является частью параметров метода:
public K SomeMethod<K, V>(V input)
{
return default(K);
}
int num1 = SomeMethod(3); // doesn't compile
int num2 = SomeMethod<int>("3"); // doesn't compile
int num3 = SomeMethod<int, string>("3"); // compiles.
Использование общего метода с интерфейсом в качестве типа ограничения.
Это пример того, как использовать общий тип TFood внутри метода Eat для класса 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; }
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);
}
}
Вы можете вызвать метод Eat следующим образом:
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
В этом случае, если вы попытаетесь позвонить:
sheep.Eat(lion);
Это будет невозможно, потому что объект-лев не реализует интерфейс IFood. Попытка выполнить вышеупомянутый вызов вызовет ошибку компилятора: «Тип« Carnivore »не может использоваться как параметр типа« TFood »в родовом типе или методе« Animal.Eat (TFood) ». Нет никакого неявного преобразования ссылок из ' Carnivore 'to' IFood '. "
ковариации
Когда IEnumerable<T>
является подтипом другого IEnumerable<T1>
? Когда T
является подтипом T1
. IEnumerable
является ковариантным в своем параметре T
, что означает, что отношение подтипа IEnumerable
идет в том же направлении, что и T
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>
Экземпляр ковариантного родового типа с заданным параметром типа неявно конвертируется в один и тот же общий тип с параметром менее производного типа.
Это соотношение выполняется потому, что IEnumerable
производит T
s, но не потребляет их. Объект, который создает Dog
s, может использоваться так, как если бы он произвел Animal
s.
Параметры типа Covariant объявляются с использованием ключевого слова out
, потому что параметр должен использоваться только как результат .
interface IEnumerable<out T> { /* ... */ }
Параметр типа, объявленный как ковариантный, может не отображаться как вход.
interface Bad<out T>
{
void SetT(T t); // type error
}
Вот полный пример:
using NUnit.Framework;
namespace ToyStore
{
enum Taste { Bitter, Sweet };
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
}
}
}
контрвариация
Когда IComparer<T>
является подтипом другого IComparer<T1>
? Когда T1
является подтипом T
IComparer
контравариантен по своему параметру T
, что означает, что отношение подтипа IComparer
идет в противоположном направлении как T
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>
Экземпляр контравариантного типового типа с заданным параметром типа неявно конвертируется в один и тот же общий тип с параметром более производного типа.
Это соотношение выполняется потому, что IComparer
потребляет T
s, но не производит их. Объект, который может сравнивать любые два Animal
s, можно использовать для сравнения двух Dog
s.
Параметры контравариантного типа объявляются с использованием ключевого слова in
, поскольку параметр должен использоваться только как вход .
interface IComparer<in T> { /* ... */ }
Параметр типа, объявленный как контравариантный, может не отображаться как выходной.
interface Bad<in T>
{
T GetT(); // type error
}
неизменность
IList<T>
никогда не является подтипом другого IList<T1>
. IList
инвариантен по своему параметру типа.
class Animal { /* ... */ }
class Dog : Animal { /* ... */ }
IList<Dog> dogs = new List<Dog>();
IList<Animal> animals = dogs; // type error
Для списков нет подтипов, потому что вы можете поместить значения в список и вывести значения из списка.
Если IList
был ковариантным, вы могли бы добавить элементы неправильного подтипа в данный список.
IList<Animal> animals = new List<Dog>(); // supposing this were allowed...
animals.Add(new Giraffe()); // ... then this would also be allowed, which is bad!
Если IList
был контравариантным, вы могли бы извлечь значения неправильного подтипа из определенного списка.
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!
Инвариантные параметры типа объявляются, опуская как ключевые слова in
и out
.
interface IList<T> { /* ... */ }
Варианты интерфейсов
Интерфейсы могут иметь параметры типа варианта.
interface IEnumerable<out T>
{
// ...
}
interface IComparer<in T>
{
// ...
}
но классы и структуры могут не
class BadClass<in T1, out T2> // not allowed
{
}
struct BadStruct<in T1, out T2> // not allowed
{
}
а также объявления общих методов
class MyClass
{
public T Bad<out T, in T1>(T1 t1) // not allowed
{
// ...
}
}
В приведенном ниже примере показаны несколько объявлений о различиях на одном интерфейсе
interface IFoo<in T1, out T2, T3>
// T1 : Contravariant type
// T2 : Covariant type
// T3 : Invariant type
{
// ...
}
IFoo<Animal, Dog, int> foo1 = /* ... */;
IFoo<Dog, Animal, int> foo2 = foo1;
// IFoo<Animal, Dog, int> is a subtype of IFoo<Dog, Animal, int>
Варианты делегатов
У делегатов могут быть параметры типа варианта.
delegate void Action<in T>(T t); // T is an input
delegate T Func<out T>(); // T is an output
delegate T2 Func<in T1, out T2>(); // T1 is an input, T2 is an output
Это вытекает из Принципа замещения Лискова , в котором говорится (среди прочего), что метод D можно считать более производным, чем метод B, если:
- D имеет равный или более производный тип возврата, чем B
- D имеет одинаковые или более общие типы параметров, чем B
Поэтому следующие присвоения являются безопасными по типу:
Func<object, string> original = SomeMethod;
Func<object, object> d1 = original;
Func<string, string> d2 = original;
Func<string, object> d3 = original;
Типы вариантов как параметры и возвращаемые значения
Если в качестве вывода появляется ковариантный тип, то содержащийся тип является ковариантным. Изготовление производителя T
s похоже на производство T
s.
interface IReturnCovariant<out T>
{
IEnumerable<T> GetTs();
}
Если контравариантный тип появляется как результат, то содержащийся тип контравариантен. Изготовление потребителя T
s подобно потреблению T
s.
interface IReturnContravariant<in T>
{
IComparer<T> GetTComparer();
}
Если в качестве ввода появляется ковариантный тип, содержащий тип контравариант. Потребление производителя T
s подобно потреблению T
s.
interface IAcceptCovariant<in T>
{
void ProcessTs(IEnumerable<T> ts);
}
Если в качестве ввода появляется контравариантный тип, то содержащийся тип является ковариантным. Потребление потребителя T
s похоже на производство T
s.
interface IAcceptContravariant<out T>
{
void CompareTs(IComparer<T> tComparer);
}
Проверка равенства общих значений.
Если логика родового класса или метода требует проверки равенства значений, имеющих общий тип, используйте свойство EqualityComparer<TType>.Default
:
public void Foo<TBar>(TBar arg1, TBar arg2)
{
var comparer = EqualityComparer<TBar>.Default;
if (comparer.Equals(arg1,arg2)
{
...
}
}
Этот подход лучше, чем просто вызов метода Object.Equals()
, поскольку реализация TBar
умолчанию проверяет, реализует ли тип IEquatale<TBar>
интерфейс IEquatale<TBar>
и если да, вызывает IEquatable<TBar>.Equals(TBar other)
. Это позволяет избежать боксирования / распаковки типов значений.
Литейное производство
/// <summary>
/// Converts a data type to another data type.
/// </summary>
public static class Cast
{
/// <summary>
/// Converts input to Type of default value or given as typeparam T
/// </summary>
/// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
/// <param name="input">Input that need to be converted to specified type</param>
/// <param name="defaultValue">defaultValue will be returned in case of value is null or any exception occures</param>
/// <returns>Input is converted in Type of default value or given as typeparam T and returned</returns>
public static T To<T>(object input, T defaultValue)
{
var result = defaultValue;
try
{
if (input == null || input == DBNull.Value) return result;
if (typeof (T).IsEnum)
{
result = (T) Enum.ToObject(typeof (T), To(input, Convert.ToInt32(defaultValue)));
}
else
{
result = (T) Convert.ChangeType(input, typeof (T));
}
}
catch (Exception ex)
{
Tracer.Current.LogException(ex);
}
return result;
}
/// <summary>
/// Converts input to Type of typeparam T
/// </summary>
/// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
/// <param name="input">Input that need to be converted to specified type</param>
/// <returns>Input is converted in Type of default value or given as typeparam T and returned</returns>
public static T To<T>(object input)
{
return To(input, default(T));
}
}
Обычаи:
std.Name = Cast.To<string>(drConnection["Name"]);
std.Age = Cast.To<int>(drConnection["Age"]);
std.IsPassed = Cast.To<bool>(drConnection["IsPassed"]);
// Casting type using default value
//Following both ways are correct
// Way 1 (In following style input is converted into type of default value)
std.Name = Cast.To(drConnection["Name"], "");
std.Marks = Cast.To(drConnection["Marks"], 0);
// Way 2
std.Name = Cast.To<string>(drConnection["Name"], "");
std.Marks = Cast.To<int>(drConnection["Marks"], 0);
Устройство чтения конфигурации с общим типом
/// <summary>
/// Read configuration values from app.config and convert to specified types
/// </summary>
public static class ConfigurationReader
{
/// <summary>
/// Get value from AppSettings by key, convert to Type of default value or typeparam T and return
/// </summary>
/// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
/// <param name="strKey">key to find value from AppSettings</param>
/// <param name="defaultValue">defaultValue will be returned in case of value is null or any exception occures</param>
/// <returns>AppSettings value against key is returned in Type of default value or given as typeparam T</returns>
public static T GetConfigKeyValue<T>(string strKey, T defaultValue)
{
var result = defaultValue;
try
{
if (ConfigurationManager.AppSettings[strKey] != null)
result = (T)Convert.ChangeType(ConfigurationManager.AppSettings[strKey], typeof(T));
}
catch (Exception ex)
{
Tracer.Current.LogException(ex);
}
return result;
}
/// <summary>
/// Get value from AppSettings by key, convert to Type of default value or typeparam T and return
/// </summary>
/// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
/// <param name="strKey">key to find value from AppSettings</param>
/// <returns>AppSettings value against key is returned in Type given as typeparam T</returns>
public static T GetConfigKeyValue<T>(string strKey)
{
return GetConfigKeyValue(strKey, default(T));
}
}
Обычаи:
var timeOut = ConfigurationReader.GetConfigKeyValue("RequestTimeout", 2000);
var url = ConfigurationReader.GetConfigKeyValue("URL", "www.someurl.com");
var enabled = ConfigurationReader.GetConfigKeyValue("IsEnabled", false);