2017-04-21 1 views
1

j'ai le code suivant écrit en C# en utilisant des interfaces, l'héritage et génériques:C# génériques covariance et contravariance conflit

public interface IBasic 
{ 

} 

public class Basic : IBasic 
{ 

} 

public class AnotherBasic : Basic 
{ 

} 

public interface IWorker<in TBasic> 
{ 
    void Run(TBasic basic); 
} 

public class Worker : IWorker<Basic> 
{ 
    public void Run(Basic basic) 
    { 
     throw new System.NotImplementedException(); 
    } 
} 

public class AnotherWorker : IWorker<AnotherBasic> 
{ 
    public void Run(AnotherBasic basic) 
    { 
     throw new System.NotImplementedException(); 
    } 
} 

public void Test() 
{ 
    List<IWorker<IBasic>> workers = new List<IWorker<IBasic>> 
    { 
     new Worker(), 
     new AnotherWorker() 
    }; 
} 

Le problème de ce code est que les classes ouvrières et anotherworker ne correspondent pas aux génériques liste des IWorker<IBasic> qui sont les parents des travailleurs tant pour le travailleur que pour la classe de base. La chose est que IWorker<in TBasic> est contravariant en raison de la signature de la méthode d'exécution, mais j'ai besoin de covariant, afin de remplir le List<IWorker<IBasic>>. La méthode d'exécution doit avoir le paramètre TBasic, et j'ai besoin de cette liste de travailleurs à des fins de modèle de conception de la chaîne de responsabilité. Est-ce que je manque quelque chose ou ai-je trouvé une raison de faire de la covariance et de la contravariance non mutuellement exclusives?

+5

Vous ne pouvez pas le faire car il ne serait pas sûr, par ex.'new List > {new AnotherWorker()} [0] .Run (new Basic())' compile mais échoue lors de l'exécution. – Lee

+3

Cela ressemble à un [XYProblem] (https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Tout problème que vous essayez de résoudre avec cette conception peut probablement être résolu avec un design différent si vous décrivez le problème réel que vous essayez de résoudre. – juharr

+0

Je peux évidemment résoudre cela avec le nombre d'approches, mais je veux que cela fonctionne comme je le montre –

Répondre

0

Ainsi, après 2 jours d'apprentissage et de recherche je répondu à ma propre question. Voici le code:

public interface IBasic {

} 

public class Basic : IBasic 
{ 

} 

public class AnotherBasic : Basic 
{ 

} 

public interface IWorker<in TBasic> 
{ 
    void Run(TBasic basic); 
} 

public class SimpleWorker : IWorker<IBasic> 
{ 
    public void Run(IBasic basic) 
    { 
     throw new System.NotImplementedException(); 
    } 
} 

public class Worker : IWorker<Basic> 
{ 
    public void Run(Basic basic) 
    { 
     throw new System.NotImplementedException(); 
    } 
} 

public class AnotherWorker : IWorker<AnotherBasic> 
{ 
    public void Run(AnotherBasic basic) 
    { 
     throw new System.NotImplementedException(); 
    } 
} 

public class Final 
{ 
    public void Test() 
    { 
     List<IWorker<AnotherBasic>> workers = new List<IWorker<AnotherBasic>> 
     { 
      new SimpleWorker(), 
      new Worker(), 
      new AnotherWorker() 
     }; 
    } 
} 

TBasic dans contravariants, ce qui signifie que la déclaration devrait être le plus précis possible, comme le montre le code: AnotherBasic Ensuite, les types qui sont moins dérivés, sont les parents, sont acceptés, et le code compile.

0

Vous pouvez l'initialiser comme ceci:

public void Test() 
{ 
    List<IWorker<IBasic>> workers = new List<IWorker<IBasic>> 
    { 
     new Worker<IBasic>(), 
     new AnotherWorker<IBasic>() 
    }; 
    workers[0].Run(new Basic()); 
} 
+0

non, cela ne fonctionne pas, à la fois travailleur et un autre travailleur ne sont pas génériques –

0

Votre déclaration des travailleurs dit « ceci est une liste des travailleurs, et chacun peut exécuter toute mise en œuvre de IBasic », ce qui est faux. Ce que vous pouvez essayer est de déplacer la responsabilité du type de commande que le travailleur peut traiter vers le travailleur lui-même (en fait, c'est ce que suggère le chain of responsibility pattern).

public interface IWorker 
{ 
    bool DidRun<TBasic>(TBasic basic); 
} 

public class WorkerChain 
{ 
    private readonly List<IWorker> workers = new List<IWorker> 
    { 
     new Worker(), 
     new AnotherWorker() 
    }; 

    public bool DidRun<T>(T basic) 
    { 
     return workers.Any(worker => worker.DidRun(basic)); 
    } 
} 

public class Worker : IWorker 
{ 
    public bool DidRun<T>(T basic) 
    { 
     if (!(basic is Basic)) 
     { 
      return false; 
     } 

     Console.WriteLine($"running {basic}"); 
     return true; 
    } 
} 

public class Test 
{ 
    public void CanRunWorkBasic() 
    { 
     var didRun = new WorkerChain().DidRun(new Basic()); 
     Debug.Assert(didRun); 
    } 
} 
0

Si vous voulez insérer vos employés dans une liste, vous aurez besoin d'une interface non générique IWorker et IWorker<TBasic> doit mettre en œuvre cette interface. Ensuite, utilisez IWorker au lieu de IWorker<TBasic> dans un List. Maintenant, il n'y a plus de problèmes avec l'ajout de vos travailleurs à un List.

De cette façon, nous avons résolu un problème, mais nous en avons malheureusement créé un autre, car nous devons implémenter la méthode Run deux fois. Une fois pour une interface non générique et une seconde fois pour l'interface générique.

Vous pouvez résoudre le problème en utilisant une classe abstraite Worker qui, par défaut quand on appelle la Run non générique, fait des contrôles neccessary, jette le paramètre et passer à la méthode Run générique. Ensuite, vos travailleurs peuvent dériver de Worker et chacun peut avoir son propre rôle.

Dans l'exemple ci-dessous, j'ai essayé de montrer comment, à mon avis, ce code devrait ressembler. La méthode non générique Run est mise en œuvre explicitly et pour être sûr j'ai également utilisé generic type constraints. Run méthode vérifie juste le type et le transmet plus loin.

public interface IBasic 
{ 

} 

public class Basic : IBasic 
{ 

} 

public class AnotherBasic : Basic 
{ 

} 

public interface IWorker 
{ 
    void Run(IBasic basic); 
} 

public interface IWorker<in TBasic> : IWorker where TBasic : IBasic 
{ 
    void Run(TBasic basic); 
} 

public abstract class Worker<TBasic> : IWorker<TBasic> where TBasic : IBasic 
{ 
    void IWorker.Run(IBasic basic) 
    { 
     if (basic is TBasic) 
      Run((TBasic)basic); 
    } 

    public abstract void Run(TBasic basic); 
} 

public class FirstWorker : Worker<Basic> 
{ 
    public override void Run(Basic basic) 
    { 
     // ... 
    } 
} 

public class SecondWorker : Worker<AnotherBasic> 
{ 
    public override void Run(AnotherBasic basic) 
    { 
     // ... 
    } 
} 


public void Test() 
{ 
    List<IWorker> workers = new List<IWorker> 
    { 
     new FirstWorker(), 
     new SecondWorker() 
    }; 
}