Поиск…


Синтаксис

  • 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);


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow