Пусть метод принимает любой тип данных в С#

У меня много модульных тестов, которые в значительной степени тестируют одинаковое поведение. Однако тип данных изменяется.

Я пытаюсь создать универсальный метод, который может принимать любой тип данных. Я попытался сделать свой входной параметр var, но это не разрешено. Кроме того, рассмотрел С# generics, но обычно имеет дело со списком.

Ответ 1

Вы можете сделать параметр a 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, a Fish, a 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.

Ответ 2

Вы можете взять object как тип параметра. Еще лучше, возможно, было бы использовать дженерики:

void MyMethod<T>(T parm) { ... }

Таким образом, этот параметр фактически относится к типу, который прошел пользователь - он не помещается в коробку, например, с помощью object и типов значений.

Ответ 3

void MyTestMethod<T>(T t) { }

дает вам общий метод тестирования, но я не могу представить, каким образом это может быть полезно. Что вам нужно проверить? Как вы знаете, тип T имеет эти методы? T может быть любым типом в указанном выше методе. Единственными методами, которые вы можете вызвать из T в приведенном выше примере, являются общие методы object.

То, что вам действительно нужно сделать, это определить общее поведение против одного или нескольких типов, которые вы хотите протестировать, и определить синтаксический контракт этого поведения через интерфейс. Затем вы можете ограничить свой общий метод тестирования, чтобы принимать только те типы, которые реализуют этот интерфейс.

interface IMyInterface
{
    void DoSomething();
} 

void MyTestMethod<T>(T t) where T : IMyInterface
{ 
    t.DoSomething();
}

Ответ 4

public void YourMethod<T>(T parameter)
{
}

Ответ 5

попробуйте использовать динамическое ключевое слово, это будет работать при условии, что все ваши разные типы имеют те же методы, которые используются вашими модульными тестами, иначе вы получите исключение во время выполнения