【问题标题】:Keeping differently typed generic objects in one collection将不同类型的通用对象保存在一个集合中
【发布时间】:2012-10-31 14:04:09
【问题描述】:

我一直在为一个项目构建一个小型访问规则模块,其中每个特定规则都由通用 Rule<TEntity> 对象反映。该规则需要一个委托来执行特定的逻辑。

有一个RulesContext 类提供方法来检查对某个实体“foo”的访问,如下所示:

rulesContext.CanIRead<Foo>(myFoo);

我的目的是将设置过程中构建的所有规则存储到一个集合中。但是我尝试的每一种方法都会导致死胡同。

我想到了类似的东西:

IDictionary<Type, Rule<object>> _rules = new Dictionary<Type, Rule<object>>();

和:

var fooRule = new Rule<Foo>(foo => foo.FullfillsACertainFooCriterion())
_rules.Add(typeof(Foo), fooRule);

CanIRead 实现将确保正确使用字典:

public bool CanIRead<TEntity>(TEntity entity)
{
    var rule = _rules[typeof(entity)];
    return rule.CanIRead(entity);
}

但编译器不喜欢这样:Rule&lt;Foo&gt; 不能分配给Rule&lt;object&gt; 类型的参数。哪种是有意义的,因为它会违反合同(这表示我可以将字典的方法与任何对象一起用作参数,这对于仅接受 Foo 类型对象的 fooRule 不成立。- Liskov 原则)

但是我想不出解决这个问题的方法。如何在一个集合中存储不同类型的 Rule 对象?

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    你能做到吗:

    [TestFixture]
    public class ContraVariance
    {
        [Test]
        public void TestNameTest()
        {
            var rules = new List<IRule<object>>(); //object is used just for demo here, probably some interface of yours is better
            rules.Add(new Rule<A>());
            rules.Add(new Rule<B>());
        }
    }
    public class A { }
    public class B { }
    
    public class Rule<TEntity> : IRule<TEntity>
    {
    
    }
    
    public interface IRule<out T>
    {
    }
    

    如果不是,我认为你必须有一个非通用的 IRule 或 RuleBase(类) 接口中的 out 关键字意味着 T 仅在外(协变),您可以阅读它here。 我想在你的情况下这将是一个问题,我怀疑规则有将 TEntity 作为参数传递的方法。

    【讨论】:

    • 哇,你正要教我一个新概念 :-) 它可以编译。必须阅读逆变器
    • 专业提示:永远不要试图记住哪个是协方差/逆方差。只有 Jon Skeet 和 Chuck Norris 可以回答这个问题,CN 把它写在了他的手上。我认为像 InOnly/OutOnly 这样更直观的东西会更好。
    • 是的,我很快就意识到我很兴奋。由于逆变关键字out 意味着T 仅用作返回类型,因此我不能将它用于我的方法参数的情况。换句话说,协变/逆变特征是完全“合法的”——它们不允许违反合同......
    • 顺便说一句-通过“违反合同”,我指的是以下规则,我忘记了它的名称:每当您定义给定基类的子类时,先决条件(特别是参数类型)子类的约束不能比基类的约束更大,后置条件(尤其是返回类型)至少要满足基类的后置条件。
    • 这可以通过你的 IRule 导出委托或为 IRule 编写扩展方法来解决
    【解决方案2】:

    您可以将值严格设为规则对象,而不是使用可以保存任何内容(例如 DateTime)的 IDictionary&lt;Type, object&gt; 作为字典中的值

    这里

    namespace RuleConsole
    {
       class Program
       {
          static void Main(string[] args)
          {
             var context = new RulesContext();
    
             var objA = new A();
             var objB = new B();
    
             context.AddRule<A>(new Rule<A>(objA));
             context.AddRule<B>(new Rule<B>(objB));
    
             Console.WriteLine(context.CanIRead<A>(objA));
             Console.WriteLine(context.CanIRead<B>(objB));
    
             Console.ReadKey();
          }
       }
    
       public interface IRule { }
       public interface IRule<T> : IRule { }
    
       public class Rule<T> : IRule<T> 
       {
          T _entity;
          public Rule(T entity)
          {
             _entity = entity;
          }
       }
    
       public class A { }
       public class B { }
    
       public class RulesContext
       {
          Dictionary<Type, IRule> _ruleDict= new Dictionary<Type, IRule>();
    
          public void AddRule<TEntity>(Rule<TEntity> rule)
          {
             _ruleDict.Add(typeof(TEntity), rule);
          }
    
          public bool CanIRead<TEntity>(TEntity entity)
          {
             var rule = (IRule<TEntity>)_ruleDict[typeof(TEntity)];
    
             //CanIRead implementation here
    
             return rule != null;
          }
       }
    }
    

    【讨论】:

    • 谢谢。我认为您的代码的核心信息是 IRule&lt;T&gt; 派生自 IRule。我会仔细看看。
    • 测试过了。非常好的和干净的方式来实现我想要的。在我最初的问题中,我提到了IDictionary&lt;Type, Rule&lt;object&gt;&gt;。只有在我自己的答案中,我将其更改为IDictionary&lt;Type, object&gt;,所以我认为您的第一个陈述是指我自己的答案。如果你把它说得更清楚一点或完成你的答案,这样我的答案就不需要先阅读,这对未来的读者来说可能是值得的。
    【解决方案3】:

    这本质上是非类型安全的。
    如果你写,你想发生什么

    _rules[typeof(Foor)].CanRead(new Bar());
    

    您需要创建一个非泛型基类或接口来存储在字典中。

    【讨论】:

    • 是的,我确实知道这一点。我很想从外面隐藏字典并确保公共方法正确处理它,但我很清楚编译器对我的计划不感兴趣:-)我仍然想知道如何正确解决这个问题 -您建议的基类不能解决问题。 object 已经是一个基类,它是最通用的。或者我没听懂你在说什么。
    【解决方案4】:

    嗯,这几乎令人尴尬 - 但我认为你刚刚帮助我解开了我的大脑:-)

    如果问题是 IDictionary&lt;Type, Rule&lt;object&gt;&gt; 过于具体,IDictionary&lt;Type, object&gt; 可以解决问题:

    var fooRule = new Rule<Foo>(foo => foo.FullfillsACertainFooCriterion())
    _rules.Add(typeof(Foo), fooRule);
    

    (与问题相同,但这次编译)

    public bool CanIRead<TEntity>(TEntity entity)
    {
        var rule = (Rule<TEntity>)_rules[typeof(entity)];
        return rule.CanIRead(entity);
    }
    

    我脑子里的障碍是,我认为Rule&lt;...&gt; 中的类型参数越通用,字典中应该允许的对象越多,但在这种情况下,情况恰恰相反:越通用论点是,合同越具体。

    拍摄:

    IDictionary<Rule<Foo>>
    

    通过将Rule 替换为其基类object,字典变得更加通用。但是,通过将Foo 替换为object,整个事情实际上变得更加专业!

    全部原因是Rule的类型参数被用作输入参数。

    这是一个重要的教训...

    【讨论】:

      猜你喜欢
      • 2011-04-02
      • 2017-06-13
      • 2018-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多