Можно ли ограничить универсальный параметр подтипом текущего объекта? - PullRequest
4 голосов
/ 17 ноября 2009

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

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

public static void DoStuff<T>(this T arg1, T arg2)

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

public static void DoStuff<T>(this T arg1, Action<T> arg2)

Однако я не могу заставить это работать с участниками. Нет такого ограничения, как это:

public void DoStuff<T>(T arg1) where T : typeof(this)

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

class Stream
{
    public void DoStuff<T>(T stream) where T : this
    {
    }
}

и тогда на подклассе было бы невозможно назвать его так:

ByteStream bs = new ByteStream()
bs.DoStuff(new Stream()) // Error! DoStuff() should be inferred as DoStuff<ByteStream>()

Есть ли способ сделать это? Я считаю, что автоматически выводить типы из аргументов, а методы расширения являются синтаксическим сахаром. И это, вероятно, почему это работает; потому что методы расширения заменяются статическими вызовами, которые затем позволяют выводить тип.

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

Чтобы уточнить. Это не просто добавление where T : MyType, потому что если я создам тип с именем MySubType, который наследуется от MyType, я смогу вызвать DoStuff в экземпляре MySubType и передать MyType в качестве параметра. Это также означает, что в случае, когда требуется Action<T>, я не смогу вызвать методы MySubType без предварительного приведения.

Ответы [ 5 ]

9 голосов
/ 17 ноября 2009

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

Ваше ограничение typeof (this) действительно должно быть this.GetType (). «typeof (this)» не имеет никакого смысла; typeof принимает тип, а не произвольное выражение.

И как только вы осознаете, что причина, по которой мы не можем сделать такое ограничение, становится более ясной. Ограничения всегда проверяются компилятором , но ясно, что this.GetType () не может быть определен до времени выполнения. Это означает, что если бы у нас была эта функция, то мы бы указали точку отказа в системе типов во время выполнения:

abstract class Animal
{
    public void Mate<T>(T t) where T : this { ... CENSORED ... }
}
...
Animal x1 = new Giraffe(); 
Mammal x2 = new Tiger();
x1.Mate<Mammal>(x2); 

Вы не можете спарить Тигра с Жирафом, но где в программе компилятор может это обнаружить? Нигде. Типы времени выполнения x1 и x2 не известны до времени выполнения, и поэтому нарушение ограничения не может быть обнаружено до тех пор.

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

Вот почему мы тщательно проектируем новые функции отклонений в C # 4, чтобы гарантировать, что они всегда безопасны для типов. (За исключением случаев, когда существующие преобразования вариантов в массивах не являются безопасными с точки зрения типов и будут по-прежнему не безопасными с точки зрения типов.) Мы хотим убедиться, что компилятор может проверять все ограничения на нарушение во время компиляции, а не выплевывать новый код, выполняющий во время выполнения. проверки, которые могут неожиданно завершиться неудачей.

3 голосов
/ 17 ноября 2009

Я думаю, что вы можете сделать это, просто указав тип в конце.

public void DoStuff<T>(T arg1) where T: YourType

Я делаю это в настоящее время в решении, но YourType является интерфейсом. Я думаю, что вы можете сделать это с конкретным классом.

1 голос
/ 17 ноября 2009

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

MySubType foo = new MySubType();
MySubType bar = new MySubType();
foo.DoStuff(bar);

// ...

public class MySubType : MyBaseType<MySubType>
{
}

public class MyBaseType<T>
{
    public void DoStuff(T arg1)
    {
        // do stuff
    }
}
1 голос
/ 17 ноября 2009

Просто используйте имя базового класса в качестве ограничения .

public void DoStuff<T>( T arg1 ) where T : BaseClass
0 голосов
/ 17 ноября 2009

Измените это на:

public void DoStuff<T>(T arg1) where T : BaseType

Таким образом, вы можете использовать только тип T, наследуемый от вашего базового типа

...