2015-01-21 2 views
2

Я пишу эмулятор, и я наткнулся на интересную ошибку:Generic тип ограничения не прилагая

error CS0311: The type 'T' cannot be used as type parameter 'T' in the generic type or method 'Emulator.Emulator<T>' . There is no implicit reference conversion from 'T' to 'Emulator.Emulator<T>.EmulatorState' .

Ошибка на следующий код (в частности Т на Emulator.IOpcode):

protected abstract class Chip8Opcode<T> : Emulator<T>.IOpcode where T : Chip8.Chip8State 

упрощенный код ниже (ideone):

public abstract class Emulator<T> where T : Emulator<T>.EmulatorState 
{ 
    public abstract class EmulatorState { } 
    public interface IOpcode 
    { 
     bool IsValid(T state); 
     void Execute(T state); 
    } 
} 

public class Chip8 : Emulator<Chip8.Chip8State> 
{ 
    public class Chip8State : EmulatorState { } 
} 

abstract class Chip8Opcode<T> : Emulator<T>.IOpcode where T : Chip8.Chip8State 
{ 
} 

в моем понимании Т ш ould должен быть ограничен Chip8State, который может быть преобразован в EmulatorState (что и требует эмулятор <>), однако, похоже, что ограничение общего типа не применяется к T в Emulator<T>, так как ошибка является «Тип« T », «а не« Тип «Chip8State» ». Является ли это ошибкой в ​​компиляции или есть лучший способ применить ограничения типа к унаследованным генерикам?

Примечание: это не дубликат подобных вопросов о столкновениях типов, поскольку T никогда не рассматривается как ограниченный тип.

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

+3

"Является ли это ошибка в компиляции?" - конечно, не в этом случае, и, откровенно говоря, в значительной степени следует предположить, что это случай, хотя и крайне редко. Во всяком случае, я не знаю, что именно вы здесь хотите, но ошибка здесь довольно ясна: 'T' не наследует тип, вложенный внутри' Emulator ', как требует ограничение; вместо этого он наследует тип, вложенный внутри 'Chip8'. Тип 'Emulator .EmulatorState' является полностью отличным типом от' Chip8.EmulatorState'. –

+0

@PeterDuniho Chip8.EmulatorState не определен, Chip8.Chip8State расширяет EmulatorState, что и требует общее ограничение на T в эмуляторе. Даже если он был отдельным, компилятор все еще не применяет ограничения к T, как показано в ошибке – Qwerty01

+0

Да, я понимаю. Я просто замалчивал имя типа. Замените 'Chip8.EmulatorState' на' Chip8.Chip8State' в моем комментарии (комментарий слишком старый для меня для редактирования) –

ответ

2

Разве вы не можете просто положить его люблю:

abstract class Chip8Opcode<T> : Emulator<Chip8.Chip8State>.IOpcode 
    where T : Chip8.Chip8State 

Кроме того, проверьте, что предмет о «ковариации и контрвариации в дженериков». Это возможно только с интерфейсами в C#.

+0

Хотя я не уверен на 100%, что это сработает позже, я не могу придумать никаких контрпримеров. Вы правы, что это недостаток ковариации в дженериках, я не могу думать о причинах, почему он не будет включен, но, возможно, они добавят его позже. – Qwerty01

+0

@ Qwerty01: ограничение из-за проблем с отклонениями в моем опыте всегда действительным ; то есть можно встретить встречный пример, где, если наследование было разрешено, компилятор разрешил бы некоторый код времени компиляции, который был бы недопустим во время выполнения (т. е. присвоение неверного типа ссылки объекта на какой-либо другой). Основываясь на вашем примере, я думаю, что рекомендация Леандро должна работать нормально; но обратите внимание, что вы также можете выразить отношения типов как интерфейсы, которые затем реализуются определенными типами. В качестве интерфейсов вы можете использовать 'in' и' out' для параметров типа, чтобы допускать дисперсию. –

+0

@ Qwerty01: обратите внимание, что я не говорю, что у меня есть контрпример с верхней части головы. Но я бы поспорил, что в этом случае можно придумать хорошие деньги. –

3

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

interface IEmulator<out T> where T : IEmulatorState 
{ ... } 
public interface IEmulatorState 
{ ... } 

, которая позволяет:

public abstract class Emulator<T> : IEmulator<T> where T : IEmulatorState 
{ ... } 

public interface IOpcode<in T> where T : IEmulatorState 
{ ... } 

и самое главное

abstract partial class Chip8Opcode<T> : IOpcode<T> where T : Chip8<T>.Chip8State 
{ ... } 
+0

И обратите внимание, что это обеспечивает шаблон для поиска встречного примера, который показывает, почему декларация не является законной без вариантов интерфейсов. То есть без 'out' и' in' здесь компилятор не может (учитывая спецификацию C#) утверждать, что вы не будете пытаться вернуть неправильный тип 'T' из интерфейса' IOpcode 'и/или не будет пытаться передать неправильный тип 'T' методу в классе' Emulator '. Объявление дисперсии явно дает компилятору необходимую ему информацию, чтобы типы работали вместе. –

+0

Я бы подумал, что есть способ сделать это в классах, но я думаю, есть причина, по которой вы не можете – Qwerty01

+2

Ну, основная причина, по которой вы не можете, состоит в том, что она просто не поддерживается в C#. Функции дисперсии являются новыми и применяются только к интерфейсам и делегатам (которые по существу являются одномерными интерфейсами). Я буду держать пари где-нибудь, Эрик Липперт написал хорошую статью, объясняющую, почему было непрактично или нежелательно допускать объявления дисперсии на родовых классах. –

Смежные вопросы