Вы можете сделать параметр object
:
public void DoSomething(object arg)
{
//...
Или вы можете сделать то, что я предпочитаю, и сделать общий метод:
public void DoSomething<T>(T arg)
{
//...
Общий подход имеет два основных преимущества, и я приведу примеры их полезности:
- Даже если вы явно не указали тип
arg
, у вас все равно есть к нему доступ.
- Вы можете добавить ограничения на типы, которые вы хотите разрешить.
И наоборот, подход object
имеет ряд важных недостатков:
- Поскольку вы воспринимаете
arg
как object
, вы сможете делать только то, что можете сделать с любым объектом.
- Если вы передадите тип значения в качестве параметра
object
, переменная будет в штучной упаковке , что означает снижение производительности. Это не огромный удар, но если вы звоните DoSomething
несколько тысяч раз подряд, вы можете начать чувствовать это.
Обобщения и типовые ограничения
Добавление ограничения типа к универсальному методу позволяет ограничить метод так, чтобы он принимал только определенные типы. Почему это полезно? Потому что даже если вы не знаете или не заботитесь о том, с каким конкретным типом вы работаете, теперь вы кое-что знаете об этом и можете использовать эту информацию.
Рассмотрим следующую настройку:
public interface IAnimal
{
void Move();
}
public class Duck : IAnimal
{
public void Move()
{
Console.WriteLine("Flying");
}
}
public class Fish : IAnimal
{
public void Move()
{
Console.WriteLine("Swimming");
}
}
public class Ant : IAnimal
{
public void Move()
{
Console.WriteLine("Walking");
}
}
Поскольку у нас есть IAnimal
интерфейс, мы можем написать универсальные методы для любой реализации IAnimal
:
public class Program
{
static void DoMove<T>(T animal) where T : IAnimal
{
animal.Move();
}
public static void Main(string[] args)
{
Duck duck = new Duck();
Fish fish = new Fish();
Ant ant = new Ant();
DoMove<Duck>(duck);
DoMove<Fish>(fish);
DoMove<Ant>(ant);
}
}
Запустите его: http://rextester.com/GOF1761
Когда мы пишем метод DoMove
, нам все равно, является ли его параметр animal
Duck
, Fish
, Ant
или чем-то еще. Все, что нас волнует, это звонить animal.Move()
. Поскольку мы использовали ограничение where T : IAnimal
, компилятор знает все, что нам нужно знать:
- Переменная
animal
имеет тип T
.
- Что бы ни было
T
, оно реализует IAnimal
.
- Все, что реализует
IAnimal
, имеет метод Move()
.
- Поэтому можно смело звонить
animal.Move()
.
(Кстати, да, мы могли бы просто написать DoMove
как static void DoMove(IAnimal animal)
, но это другое обсуждение.)
Вывод типа (и некоторые его последствия)
Хорошо, но давайте сделаем еще один шаг. Во многих случаях вы можете вызывать универсальные методы без указания параметров их типа. Это называется вывод типа , и, кроме того, что вы экономите время на вводе, это может быть полезно при выполнении одной и той же операции над объектами разных типов.
public static void Main(string[] args)
{
IAnimal[] animals = new IAnimal[]
{
new Duck(),
new Fish(),
new Ant()
};
foreach (IAnimal animal in animals)
{
DoMove(animal);
}
}
Запустите его: http://rextester.com/OVKIA12317
Вам нужно всего лишь написать метод DoMove<T>
один раз, и вы можете вызывать его для любого типа IAnimal
без указания более конкретного типа. Соответствующая версия Move будет вызываться каждый раз, потому что DoMove<T>
может определить, какой тип использовать для T
. Когда вы вызываете DoMove(duck)
, .NET понимает, что вы действительно имеете в виду DoMove<Duck>(duck)
, который затем вызывает метод Move
в классе Duck
.