Запретить определенную иерархию типов, используемых для универсального параметра - PullRequest
5 голосов
/ 18 мая 2011

Я начал этот вопрос с загрузки фона рассматриваемых типов;интерфейсы и обоснование архитектуры.

Тогда я понял - «Это ТАК - будь проще и доберись до сути».

Итак, пошло.

Iиметь такой класс:

public class AGenericType<T> : AGenericTypeBase
{
  T Value { get; set; }
}

.Net, тогда, конечно, я могу сделать это:

AGenericType<AGenericType<int>> v;

Однако, в контексте использования AGenericType<T>,бессмысленно делать это так же, как бессмысленно делать это:

Nullable<Nullable<double>> v;

Я хочу иметь возможность ограничить этот универсальный тип, чтобы создать такойэкземпляр или даже объявляют ссылку на тип, когда его T является производным от AGenericTypeBase - предпочтительно во время компиляции.

Теперь интересно то, что приведенный здесь пример Nullable<T> действительно генерирует ошибку компилятора.Но я не могу понять, как Nullable<T> ограничивает T типами, отличными от Nullable<T> - поскольку Nullable<T> является структурой и единственным общим ограничением, которое я могу найти (даже в IL, который часто дает секреты компиляторакак у делегатов) where T:struct.Так что я думаю, что нужно быть взломщиком компилятора ( РЕДАКТИРОВАТЬ: см. Ответ @Daniel Hilgarth + комментарии ниже для небольшого исследования этого ).Взлом компилятора, конечно, я не могу повторить!

Для моего собственного сценария IL и C # не допускают ограничение отрицательного утверждения вроде этого:

public class AGenericType<T> : where !T:AGenericTypeBase

(обратите внимание на '!' В ограничении)

Но какую альтернативу я могу использовать?

Я думал о двух:

1) Исключение времени выполнения, сгенерированное вконструктор AGenericType<T>:

public AGenericType(){
  if(typeof(AGenericBase).IsAssignableFrom(typeof(T)))
    throw new InvalidOperationException();
}

Это на самом деле не отражает природу ошибки - потому что проблема заключается в универсальном параметре и, следовательно, в целом типе;не только этот экземпляр.

2) Итак, вместо этого то же исключение времени выполнения, но сгенерированное в статическом инициализаторе для AGenericType<T>:

static AGenericType(){
  if(typeof(AGenericBase).IsAssignableFrom(typeof(T)))
    throw new InvalidOperationException();
}

Но затем я столкнулся сПроблема в том, что это исключение будет заключено в TypeInitializationException и потенциально может вызвать путаницу (по моему опыту разработчики, которые фактически читают всю иерархию исключений, тонки на земле).

Для меня это очевидный случайдля общих ограничений «отрицательного утверждения» в IL и C # - но поскольку это вряд ли произойдет, что бы вы сделали?

Ответы [ 3 ]

2 голосов
/ 18 мая 2011

О Nullable<T>: ограничение T : struct является причиной ошибки компилятора, потому что переменная структуры никогда не может быть нулевой, тогда как переменная типа Nullable<T> может быть нулевой.
Таким образом, является хаком компилятора, но только таким образом, что Nullable<T> обрабатывается как тип значения, допускающий значение NULL, а не тип значения, не допускающий значения NULL.

public struct Nullable2<T> where T: struct
{
}

// Both lines will generate the same error
Nullable2<Nullable<int>> v;
Nullable<Nullable<int>> v2;

Я хочу сказать следующее:
Nullable<T> не выполняет "общих ограничений" отрицательного утверждения ".

1 голос
/ 21 мая 2011

В конце я сделал исключение в статическом инициализаторе универсального типа, поскольку он более четко демонстрирует, что что-то не так с использованием вызывающим типом в отличие от переданного аргумента времени выполнения конструктору. Вызывающая сторона легко исправляет ошибку, поэтому я считаю ее лучшим компромиссом.

1 голос
/ 18 мая 2011

Надеюсь, я ясно понял вашу проблему, и, полагаю, вам нужен интерфейс маркера.

Как вы можете гарантировать, что какой-то универсальный параметр T не наследуется от какого-то класса?Ну, так как вы не можете этого сделать, вы можете создать интерфейс маркера под названием «IWhothing» (замените «Whither» своим собственным именем, которое соответствует вашим потребностям) и используйте его для различения видов A:

public class B<T> 
   where T : A, IWhatever

T наследует A, но A должен реализовывать IWh независимо.

Вы должны реализовать столько интерфейсов маркеров, сколько вам нужно, чтобы иметь различные T виды.

Обновление:

Это как-то связано с как ограничение T не должно наследовать A ?

Абсолютно.Если мне это нужно, я использую маркерные интерфейсы: IIsNotA, и вы можете иметь это ограничение: T : IIsNotA.

Это всего лишь пример.В реальном сценарии IIsNotA будет вызываться с правильным идентификатором, связанным с некоторой иерархией классов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...