Проверка значения во время компиляции для реализации нескольких интерфейсов


Предположим, я хочу выразить"метод, который ожидает объект, реализующий интерфейсы IFoo и IBar ". Что-то вроде:

void Method(IFoo+IBar param);

Как я могу сказать это, используя C# ?

(существует ли синтаксическая конструкция? Или, может быть, хорошая идиома?)


Попытка

Введем интерфейс:

interface IFooBar : IFoo, IBar {}

void Method(IFooBar param);

Это плохо и я сожалею даже думать об этом: -) выглядит нормально на первый взгляд, но грустная часть заключается в том, что молча вводит зависимость, которой здесь быть не должно.

Единственная причина существования IFooBar состоит в том, чтобы быть частью интерфейса Method. Следовательно, любой класс объектов, которые будут использоваться в качестве параметра Method, должен знать, что этот метод (и этот интерфейс) существуют , чего в противном случае не было бы. Все классы, реализующие IFoo и IBar, должны быть изменены, чтобы также реализовать IFooBar (возможно, знакомство с новой сборкой) - что крайне непрактично или даже невозможно, если мы не можем изменить их.

Ненужная зависимость = запрет.

Решение

Откажитесь от статического набора текста:

void Method(object param)
{
    if (!param is IFoo)
        throw new ArgumentException("IFoo not supported", "param");
    if (!param is IBar)
        throw new ArgumentException("IBar not supported", "param");

    // ...
}

(или: определите один тип в сигнатуре и динамически проверьте другие - предпочтительно, если один из них самый важный, но еще более запутанный)

Работает корректно, но во время выполнения (без проверки во время компиляции). Отчаянно нуждается в доке (и все его читают).

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


Реальный случай для этой ситуации? Например:IList<Foo> + INotifyCollectionChanged.

4   4   2012-04-27 17:50:17

4 ответа:

public void someMethod<T>(T param) where T : IFoo, IBar
{...}

Один мой друг однажды задал Эрику Липперту очень похожий вопрос, и вот его ответ:]}

Q: есть ли причина, по которой C# не позволяет объявлять переменную с помощью несколько интерфейсов?

О: давайте скажем, что мы комментировать его как {IFoo, Ибар}.

Проблема больше, чем объявление переменных. По существу, вы есть говоря, что вы хотите изменить ограничение на переменную из "the переменная должна быть типа" в "переменной должно быть все от несколько приведенных типов". Но зачем останавливаться на переменных? Если это тот самый тип ограничение переменной, то это должен быть тип, точка. Вы должен уметь сказать:

List< { IFoo, IBar } > myList; Or
 public static { IFoo, IBar } MyMethod( { IFoo, IBar }[ ] foobars  )
 {  return foobars[0];  }

И так далее. Нет смысла делать функцию наполовину.

Это было бы главным расширением системы типов. Мы бы хотели этого. поддерживайте его в каждом управляемом языке, а не только в C# и VB. Это вид функции, которую вы хотите получить в рамках в версии один; это это очень дорого и сложно сделать такое серьезное изменение когда у вас четыре версии и миллиарды линий клиентов код, который должен продолжать работать.

Я сам часто хотел эту особенность; я согласен, что это хороший особенность, но я думаю, что это просто слишком дорого, чтобы добавить его сейчас. Следующий когда вы разрабатываете систему нового типа, включите эту функцию из начало!

Вы можете достичь этого, определив свой метод следующим образом

void Method<T>(T param) 
    where T: IFoo, IBar
{
}

Надеюсь, это поможет.

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

void foo(T param) where T:IFoo,IBar {...}

Легко вызвать, если во время компиляции известно, что T является некоторым конкретным типом, который реализует IFoo и IBar. К сожалению, трудно построить коллекцию объектов, которые могут быть переданы в процедуру, подобную приведенной выше, без того, чтобы все они не имели общего базового типа, который реализует оба интерфейса. Если кто-то хочет попробовать провести собрание "вещи, которые реализуют и IFoo, и IBar, и могут быть переданы подпрограммам, ожидающим того же", есть способы сделать это, но они не просты.

Если можно заранее определить все подпрограммы, которым можно было бы передать данные объекты, то можно заставить коллекцию создавать закрытые универсальные делегаты для каждого объекта, который был задан, в то время, когда он был задан. Это сработает, и может быть разумно эффективно, в тех ситуациях, когда это выполнимо. Однако он вводит очень сильные зависимости между классом коллекции и использованием объектов в коллекции.

Несколько более универсальный подход состоит в том, чтобы иметь коллекцию объектов-оболочек, которые поддерживают метод для выполнения произвольных ограниченных-открытых-универсальных действий над своими внутренними объектами. К сожалению, делегаты не будут хорошо работать для этого, так как .net не предоставляет никакого механизма для вызова open generic делегаты без предварительного использования отражения для преобразования их в закрытую форму. Вместо этого необходимо определить интерфейсы, такие как:
interface IActUpon<T,U> {
  void DoSomething<MT>(ref MT it) where MT:T,U;
}
interface IActUpon<T,U,PT1> {
  void DoSomething<MT>(ref MT it, ref PT1 p1) where MT:T,U;
}
interface IActUpon<T,U,PT1,PT2> {
  void DoSomething<MT>(ref MT it, ref PT1 p1, ref PT2 p2) where MT:T,U;
}

Если объект поддерживает семейство методов:

void DoSomethingWithMe(IActUpon<T,U> proc);
void DoSomethingWithMe(IActUpon<T,U,PT1> proc, ref PT1 p1);
void DoSomethingWithMe(IActUpon<T,U,PT1,PT2> proc, ref PT1 p1, ref PT2 p2);
Можно заставить объект вызвать любую желаемую процедуру, которая требует параметра, ограниченного типами T и U, создав простой класс, который реализует соответствующую вариацию IActUpon<T,U,...>.

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