泛型类型相互引用

4
我写了一个简单的解析器,想要实现下面两个接口:
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>'.

@CodingYoshi 我不想深入了解情况,因为我想了解为什么这段代码无法编译。我认为有一个根本的原因,我现在还不理解。 - Nikita Sivukhin
错误很明显,它表示无法将 IResult<TValue, TToken> 转换为 IResult<TValue, ITokenizer<IResult<TValue, TToken>, TValue>>。我不确定你还想知道什么? - CodingYoshi
@Evk 但是为什么像 public interface Circular<T> where T : Circular<T> 这样的简单循环引用是被允许并且可以工作的呢? - Nikita Sivukhin
@CodingYoshi 但为什么它不能转换呢?我可以简单地推断出,IResult<TValue, TToken> 是从约束中的 IResult<TValue, ITokenizer<IResult<TValue, TToken>, TValue> - Nikita Sivukhin
@PavelVoronin 是的,我想是这样。但如果我只是用 out 关键字标记类型参数,仍然会有编译错误...或者您建议其他方法? - Nikita Sivukhin
显示剩余4条评论
2个回答

5
你可以尝试给两个接口都添加一个额外的类型约束,像这样:

你可以尝试给两个接口都添加一个额外的类型约束,像这样:

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> {

}

我认为主要问题并不是循环引用,而是编译器无法自动推导你的泛型类型之间的隐式转换,除非你对此进行一些帮助。
更新: 我认为您的接口缺乏 Tokenizer 和 Result 之间的强关系。IResult 接口表明 TToken 可以是任何分词器,我指的是与 任何结果相关联。因此,它可以是 ITokenizer<Result1>ITokenizer<Result2> 等等。但您不能将 ITokenizer<Result1> 赋给 ITokenizer<Result2>(即使结果实现了相同的接口)- 这是不同的类型。 分词器接口也是如此。当您像上面那样更改接口时,现在清楚了 TToken 是 TResult 的分词器,同时TResultTTokenizer 的结果(现在这些是两个具体类型,而不是接口,它们之间有着强烈的关系)。

@NikitaSivukhin 请参考我的想法,关于原因(我认为这不是编译器无法推断,而是这些定义确实错误,不应该编译)。 - Evk

0

更新 请忽略这个回答,因为Evk的回答否定了这个回答。但是,我仍然把这个回答放在这里,因为如果有人认为它与循环引用有关,它将有助于清楚地解释它并不是。

问题在于编译器在尝试编译第一个接口时,需要编译第二个接口,但要编译第二个接口,需要先编译第一个接口。因此,由于无法得出结论,它无法这样做。为了使事情更简单,这段代码将会得到与你的同样的错误:

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>>
{ }

看起来@Evk反驳了你关于循环引用的论点 :-) - Nikita Sivukhin
1
他完全做到了。我正在努力理解它。 - CodingYoshi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接