2016-11-19 2 views
2

я пишу простой синтаксический анализатор и хотите реализовать следующий два интерфейса:Обобщенные типы ссылок друг на друга

public interface IResult<TValue, TToken> 
    where TToken : ITokenizer<IResult<TValue, TToken>, TValue> 
{ 
    TToken Tokenizer { get; } 
    TValue Value { get; } 
} 

public interface ITokenizer<TResult, TValue> 
    where TResult : IResult<TValue, ITokenizer<TResult, TValue>> 
{ 
    TResult Advance(); 
} 

Он имеет следующую цель: ITokenizer непреложный класс для разбиения строки на лексемы. Мы можем вызвать метод Advance и получить Result: следующий токен и следующий токенизатор. Итак, я хочу, чтобы токен и токензатор хранилища находились в классе Result, и для этого нужно добавить ограничение времени компиляции.

Теперь у меня есть ошибка времени компиляции при конструировании этих двух интерфейсов.

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

public class Result : IResult<string, Tokenizer> 
{ /* implement interface */} 

public class Tokenizer : ITokenizer<Result, string> 
{ /* implement interface */} 

Может кто-нибудь объяснить, что случилось? Может быть, почему это невозможно или как сделать этот код правильным?

P.S. Для моей задачи я могу просто использовать интерфейс IResult<TValue, TToken> без каких-либо ограничений, но могу ли я реализовать это без потери ограничений?

ошибки компилятора:

(3:22) The type 'Test.IResult<TValue,TToken>' cannot be used as type parameter 'TResult' in the generic type or method 'Test.ITokenizer<TResult,TValue>'. 
There is no implicit reference conversion from 'Test.IResult<TValue,TToken>' to 
'Test.IResult<TValue,Test.ITokenizer<Test.IResult<TValue,TToken>,TValue>>'. 
(10:22) The type 'Test.ITokenizer<TResult,TValue>' cannot be used as type parameter 'TToken' in the generic type or method 'Test.IResult<TValue,TToken>'. 
There is no implicit reference conversion from 'Test.ITokenizer<TResult,TValue>' to 
'Test.ITokenizer<Test.IResult<TValue,Test.ITokenizer<TResult,TValue>>,TValue>'. 
+0

2 вещи: добавьте ошибку компиляции на свой пост, чтобы мы знали, что это такое, а во-вторых, возможно, сообщите нам, что вы пытаетесь сделать, чтобы мы знали, почему вы выбрали это решение. Там может быть лучшее решение, и вы получите еще несколько идей. – CodingYoshi

+0

@CodingYoshi Я не хочу углубляться в ситуацию, потому что хочу понять, почему этот код не компилируется. Я думаю, что для этого есть основополагающая причина, которую я сейчас не понимаю. –

+0

Но у вас есть круглые ссылки: ограничения типа IResult зависят от ITokenizer и vica versa. – Evk

ответ

2

Вы можете попробовать добавить еще один тип ограничения для обоих интерфейсов, например:

public interface IResult<TValue, TToken, TResult> 
    where TToken : ITokenizer<TResult, TValue, TToken> 
    where TResult : IResult<TValue, TToken, TResult> { 
    TToken Tokenizer { get; } 
    TValue Value { get; } 
} 

public interface ITokenizer<TResult, TValue, TTokenizer> 
    where TResult : IResult<TValue, TTokenizer, TResult> 
    where TTokenizer : ITokenizer<TResult, TValue, TTokenizer> { 
    TResult Advance(); 
} 

Это немного более некрасиво, но я думаю, что будет работать ваша цель:

public class Result : IResult<string, Tokenizer, Result> 
{ 

} 

public class Tokenizer : ITokenizer<Result, string, Tokenizer> { 

} 

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

UPDATE: Я думаю, что ваши интерфейсы не имеют сильных связей между Tokenizer и Result. Интерфейс IResult говорит, что TToken может быть any tokenizer, я имею в виду связанный с любой Результат. Так может быть ITokenizer<Result1>, ITokenizer<Result2> и так далее. Но вы не можете назначить ITokenizer<Result1>ITokenizer<Result2> (хотя результаты реализуют один и тот же интерфейс) - это разные типы. То же самое верно для интерфейса токенизатора. Когда вы меняете интерфейс, как показано выше, теперь становится ясно, что TToken является токенизатором TResult, и в то же время TResult является результатом TTokenizer (теперь это два конкретных типа, а не интерфейсы, с сильной связью между ними).

+0

Спасибо! Но для меня неспособность вывести, что для компилятора остается неясной ... –

+0

@NikitaSivukhin также вижу мои мысли о причинах (я думаю, что это не компилятор не может вывести, но эти определения действительно ошибочны и не должны компилироваться). – Evk

0

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

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

public interface IFirst<TFirst> 
    where TFirst : ISecond<IFirst<TFirst>> 
{ 

} 

public interface ISecond<TSecond> 
    where TSecond : IFirst<ISecond<TSecond>> 
{ } 

Но код ниже, не получит ошибки, так как нет циклических ссылок и компилятор может прийти к выводу:

public interface IFirst<TFirst> 
    where TFirst : ISecond<IFirst<TFirst>> 
{ 

} 

public interface ISecond<TSecond> 
    //where TSecond : IFirst<ISecond<TSecond>> 
{ } 
+0

Похоже, @Evk опроверг ваш аргумент для круговых ссылок :-) –

+1

он полностью сделал. Я пытаюсь обвести вокруг себя голову. – CodingYoshi

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