Ограничение интерфейса для общих аргументов метода
В моем стремлении понять С# правильно, я спрашиваю, какие практические различия между указанием ограничения интерфейса на аргументе generic метода и просто указанием интерфейса как типа аргумента?
public interface IFoo
{
void Bar();
}
public static class Class1
{
public static void Test1<T> (T arg1) where T : IFoo
{
arg1.Bar();
}
public static void Test2(IFoo arg1)
{
arg1.Bar();
}
}
ИЗМЕНИТЬ
Я знаю, что мой пример очень узкий, как пример. Меня интересуют различия, выходящие за рамки его возможностей.
Ответы
Ответ 1
В вашем конкретном примере нет разницы. Но возьмите следующий метод:
public static class Class1
{
public static T Test1<T>(T arg1) where T : IFoo
{
arg1.Bar();
return arg1;
}
public static IFoo Test2(IFoo arg1)
{
arg1.Bar();
return arg1;
}
}
Test1
вернет определенный тип arg1, тогда как Test2
вернет интерфейс. Это часто используется в беглых интерфейсах.
Расширенный пример:
public interface IFoo
{
void Bar();
}
public class Foo : IFoo
{
// implementation of interface method
public void Bar()
{
}
// not contained in interface
public void FooBar()
{
}
}
var foo = new Foo();
Class1.Test1(foo).FooBar(); // <- valid
Class1.Test2(foo).FooBar(); // <- invalid
Ответ 2
Для примера, который вы указали, нет разницы. С другой стороны, использование общей версии дает возможность расширить список ограничений (where T : IFoo, IOther
) в будущем без изменения сигнатуры метода.
Ответ 3
Я хотел бы обратить особое внимание на ответы, которые другие дали.
Существует разница между Test(IFoo foo)
и Test<T>(T foo) where T : IFoo
. Там есть реальная разница, так же как и огромная разница между List<object>
(или, скажем, ArrayList
, которая получает object
) и List<string>
.
Test (IFoo foo)
, дает вам преимущества полиморфизма и наследования типов, как и List<object>
. Он позволяет создавать один класс, который обрабатывает все типы IFoo
. Но иногда мне не нужен полиморфизм, я хочу список, который может содержать только строки, и List<string>
дает мне это, не требуя, чтобы я написал сильно типизированную оболочку над ArrayList
.
То же самое для вашего кода. Скажем, у меня есть class Comparer<T> where T:IFoo
. Я хочу иметь возможность использовать этот класс для сравнения объектов Foo1
друг с другом или сравнить Foo2
друг с другом, но я не хочу сравнивать Foo1
с Foo2
. Сильно типизированный общий метод обеспечит это, в то время как полиморфный не будет:
public class Comparer
{
public bool Compare1<T>(T first, T second) where T : IFoo {...}
public bool Compare2 (IFoo first, IFoo second) {...}
}
Foo1 first = new Foo1();
Foo2 second = new Foo2();
myComparer.Compare1(first, second); // won't compile!
myComparer.Compare2(first, second); // Compiles and runs.
Ответ 4
Все дело в литье. Если ваш метод должен возвращать T, тогда Test1 не требует кастинга, и поскольку Test2 возвращает только интерфейс, вам понадобится явное или неявное литье типов, чтобы получить окончательный тип.
Ответ 5
Часто ограничение интерфейса сочетается с, например, IFoo, new()
... который позволяет вам полностью манипулировать объектами как T
, создавать, инициализировать коллекции и т.д. - и возвращать T, как было предложено. Хотя с помощью интерфейса вы не знаете, какой класс (T) действительно.