Подтвердить что ты не робот

Ограничение интерфейса для общих аргументов метода

В моем стремлении понять С# правильно, я спрашиваю, какие практические различия между указанием ограничения интерфейса на аргументе 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();
    }
}

ИЗМЕНИТЬ

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

4b9b3361

Ответ 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) действительно.