2016-11-19 3 views
2

Je vous écris analyseur simple et veulent mettre en œuvre deux interfaces suivantes:références types génériques les uns des autres

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(); 
} 

Il a ensuite objectif: ITokenizer est une classe immuable pour diviser la chaîne par des jetons. Nous pouvons appeler la méthode Advance et obtenir Result: jeton suivant et tokenizer suivant. Donc, je veux un jeton de stockage et un tokenizer dans la classe Result et je veux ajouter une contrainte de compilation pour cela.

Maintenant, j'ai une erreur de compilation lors de la construction de ces deux interfaces.

Je pensais que les classes suivantes peuvent implémenter des interfaces avec toutes les contraintes:

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

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

Quelqu'un peut-il expliquer ce qui ne va pas? Peut-être pourquoi c'est impossible ou comment rendre ce code correct?

P.S. Pour ma tâche je peux simplement utiliser l'interface IResult<TValue, TToken> sans aucune contrainte, mais puis-je l'implémenter sans perdre de contraintes?

erreurs de compilation:

(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 choses: S'il vous plaît ajouter l'erreur de compilation à votre poste afin que nous sachions ce que c'est et d'autre part peut-être nous dire ce que vous essayez de faire, nous savons pourquoi vous avez choisi cette solution. Il y a peut-être une meilleure solution et vous aurez plus d'idées. – CodingYoshi

+0

@CodingYoshi Je ne veux pas aller au fond de la situation parce que je veux comprendre pourquoi ce code ne compile pas. Je pense qu'il y a une raison fondamentale sous-jacente à cela, que je ne comprends pas maintenant. –

+0

Mais vous avez ici une référence circulaire: Les contraintes de type IResult dépendent de ITokenizer et vice versa. – Evk

Répondre

2

Vous pouvez essayer d'ajouter une contrainte de type à deux interfaces, comme ceci:

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(); 
} 

Il est un peu plus laid, mais je pense que va travailler pour votre objectif:

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

} 

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

} 

Je pense que le principal problème est pas de références circulaires, mais juste le compilateur fait peut pas d educe conversion implicite entre vos types génériques jusqu'à ce que vous l'aidez un peu.

MISE À JOUR: Je pense que vos interfaces manquent de relation forte entre Tokenizer et Result. IResult interface dit que TToken peut être tout tokenizer, je veux dire lié à tout résultat. Donc, il peut être ITokenizer<Result1>, ITokenizer<Result2> et ainsi de suite. Mais vous ne pouvez pas affecter ITokenizer<Result1> à ITokenizer<Result2> (même si les résultats implémentent la même interface) - ce sont des types différents. La même chose est vraie pour l'interface tokenizer. Lorsque vous changez d'interface comme ci-dessus, il est maintenant clair que TToken est tokenizer de TResult, et en même temps TResult est le résultat de TTokenizer (maintenant ce sont deux types concrets, pas des interfaces, avec une forte relation entre eux).

+0

Merci! Mais pour moi, l'incapacité de déduire cela pour compilateur reste floue ... –

+0

@NikitaSivukhin voir aussi mes pensées sur les raisons (je pense que ce n'est pas compilateur ne peut pas déduire, mais ces définitions sont vraiment faux et ne devraient pas compiler). – Evk

0

Mise à jour Ne tenez pas compte de cette réponse car la réponse d'Evk a invalidé cette réponse. Cependant, je laisse toujours cette réponse ici parce que si quelqu'un d'autre pense qu'il s'agit d'une référence circulaire, cela aidera à expliquer que ce n'est pas le cas.

Le problème est lorsque le compilateur tente de compiler la première interface, il doit compiler le second, mais pour compiler le second, il doit compiler le premier. Par conséquent, il ne peut pas le faire puisqu'il ne peut pas arriver à une conclusion.Pour rendre les choses plus simples, ce code aura la même erreur que le vôtre:

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

} 

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

Mais le code ci-dessous ne se erreurs car il n'y a pas de référence circulaire et le compilateur peut arriver à une conclusion:

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

} 

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

On dirait que @Evk réfute votre argument pour des références circulaires :-) –

+1

il l'a fait totalement. J'essaye d'envelopper ma tête autour d'elle. – CodingYoshi