Мне сложно понять, почему было бы полезно сделать что-то вроде этого: (Образец - это класс)
static void PrintResults<T>(T result) where T : Sample
Не было бы лучше просто передать Sample в метод?
static void PrintResults (Sample result)
Мне сложно понять, почему было бы полезно сделать что-то вроде этого: (Образец - это класс)
static void PrintResults<T>(T result) where T : Sample
Не было бы лучше просто передать Sample в метод?
static void PrintResults (Sample result)
Я рекомендую избегать типичных типов, в которых работает не общий синтаксис, например, приведенный вами пример. Однако есть и другие полезные случаи.
Например, указывая тип возврата в общем случае:
static T Create<T>() where T: Sample, new()
{
return new T();
}
// Calling code
Sample sample = Create<Sample>();
вместо
static object Create()
{
return new Sample();
}
// Calling code
Sample sample = (Sample) Create();
Вы также можете использовать шаблоны для установки нескольких ограничений на тип. Например:
static T Create<T>() where T: IMyInterface, new()
{
return new T();
}
interface IMyInterface {}
class MyClass : IMyInterface { }
// Calling code.
MyClass myClass = Create<MyClass>();
Это позволяет генерировать новый тип, который реализует определенный интерфейс и имеет общий конструктор. Также:
static void DoSomething<T>(T t) where T: IMyInterface1, IMyInterface2
{
t.MethodOnIMyInterface1();
t.MethodOnIMyInterface2();
}
interface IMyInterface1
{
void MethodOnIMyInterface1();
}
interface IMyInterface2
{
void MethodOnIMyInterface2();
}
class MyClass: IMyInterface1, IMyInterface2
{
// Method implementations omitted for clarity
}
// Calling code
MyClass myclass'
DoSomething(myclass); // Note that the compiler infers the type of T.
Если вы можете потребовать несколько интерфейсов по одному параметру без (1) создания нового типа, который реализует все эти интерфейсы, и (2) требующих наличия параметров этого типа.
Как отмечает @dcastro в своем ответе, общие типы также могут сказать, что компилятор требует, чтобы типы были одинаковыми. Например:
static void DoSomething<T>(T t1, T t2) where T: MyType
{
// ...
}
class MyType {}
class MyType1: MyType {}
class MyType2: MyType {}
// Calling code
MyType1 myType1;
MyType2 myType2;
DoSomething<MyType>(myType1, myType2);
Если компилятор требует, чтобы t1 и t2 были одного типа, но могут быть любыми типами, наследующими MyType
. Это полезно в системах автоматизированного модульного тестирования, таких как NUnit или MSTest, для общих проверок равенства и сравнения.
В большинстве ответов предлагаются объяснения полезности дженериков с интерфейсами, которые, по-видимому, не затрагивают ваш фактический вопрос.
Правда, для примера, который вы опубликовали, нет никакой пользы от использования универсального метода. Это на самом деле хуже, потому что это приведет к созданию нескольких реализаций одной и той же функции и когда-либо слегка увеличит размер кода во время выполнения.
В пустотах вы всегда можете использовать интерфейс в качестве параметра, чтобы работать с несколькими типами, поэтому здесь часто полезны generics arent.
Только исключения - это ограничения для генериков. И этим я не имею в виду что-то вроде где T: IA, IB так как это может быть сделано с помощью интерфейса, а также реализует IA и IB. В какой-то момент это будет утомительно, так как вам понадобится больше и больше интерфейсов. Поэтому давайте рассмотрим класс "специальные ограничения" и новый
public void AddNew(List<T> items) where T : new
{
items.Add(new T());
}
и класс, который полезен, если метод мутирует его параметр, который не работает для structs
static void IncA<T>(T item) where T: class, IA
{
item.A++;
}
Реальная мощность дженериков заключается в том, что методы имеют общий тип возвращаемого значения или общие классы, такие как List <T> . Вы не хотите внедрять новый класс для каждого списка, который вам нужен.