【问题标题】:Creating a collection of generic generics - Dictionary covariance创建泛型泛型的集合 - 字典协方差
【发布时间】:2014-03-21 22:30:53
【问题描述】:

我不确定如何更好地表达这个问题,但我在尝试多次创建通用接口字典时遇到了以下问题。这通常是在尝试创建处理不同类型的注册表类型集合时发生的:

namespace GenericCollectionTest
{
    [TestFixture]
    public class GenericCollectionTest
    {
        interface IValidator<T>
        {
            bool Validate(T item);
        }

        class TestObject
        {
            public int TestValue { get; set; }
        }

        private Dictionary<Type, IValidator<object>> Validators = new Dictionary<Type, IValidator<object>>();

        class BobsValidator : IValidator<TestObject>
        {
            public bool Validate(TestObject item)
            {
                if (item.TestValue != 1)
                {
                    return false;
                }
            }
        }

        [Test]
        public void Test_That_Validator_Is_Working()
        {
            var Test = new TestObject {TestValue = 1};
            Validators.Add(typeof(BobsValidator), new BobsValidator());

            Assert.That(Validators[typeof(TestObject)].Validate(Test));
        }
    }
}

但是,编译失败,因为 BobsValidator 不能分配给参数类型 IValidator。基本上,我不需要验证器之外的类型安全,但是一旦我进入其中,我就不需要接口的使用者将其强制转换为他们想要使用的类型。

在 Java 中,我可以:

Dictionary<Type, IValidator<?>>

我知道我可以做这样的事情(ala IEnumerable):

interface IValidator
{
    bool Validate(object item);
}

interface IValidator<T> : IValidator
{
    bool Validate(T item);
}

abstract class ValidatorBase<T> : IValidator<T>
{
    protected bool Validate(object item)
    {
        return Validate((T)item);
    }

    protected abstract bool Validate(T item);
}

然后让字典采用 IValidator 并扩展 ValidatorBase,但似乎必须有更好的方法,我没有看到。或者,这只是整体设计不佳?看来我需要这样的结构:

WhatTheHecktionary<T, IValidator<T>>

谢谢!

【问题讨论】:

  • 您的抽象版本是一个很好的起点。从那里您只需要一个可以在运行时为您提供正确实现的工厂。问题是您可以向调用代码公开的只是 IValidator,因此每个实现都必须具有该方法。

标签: c# generics collections idictionary


【解决方案1】:

为了将 BobsValidator 分配给 IValidator,您需要将接口泛型参数设为协变,这将允许您的 IValidator 指向更具体的类型,例如 IValidator。

interface IValidator<out T>
{
   bool Validate(T item);
}

但是,您会意识到您无法编译,因为您的接口不再是类型安全的,因此编译器不会允许。那么为什么它不再是类型安全的呢?想象一下:

using NUnit.Framework;
namespace GenericCollectionTest
{
    [TestFixture]
    public class GenericCollectionTest
    {
        //.NET Compiling Error:
        //"Invalid variance: The type parameter 'T' must be contravariantly valid ..."
        interface IValidator<out T>
        {
            //Error: "Parameter must be type-safe. Invalid variance..."
            bool Validate(T item);
        }

        class MyObject
        {
            public int TestValue { get; set; }
        }

        class YourObject
        {
            public int CheckValue { get; set; }
        }

        class MyValidator : IValidator<MyObject>
        {
            public bool Validate(MyObject item)
            {
                return (item).TestValue == 1;
            }
        }

        class YoursValdator : IValidator<YourObject>
        {
            public bool Validate(YourObject item)
            {
                return (item).CheckValue == 1;
            }
        }

        [Test]
        public void Test_That_Validator_Is_Working()
        {
            //.NET compiler tries to prevent the following scenario:

            IValidator<object> someObjectValidator = new MyValidator();
            someObjectValidator.Validate(new YourObject()); // Can't use MyValidator to validate Yourobject

            someObjectValidator = new YoursValdator();
            someObjectValidator.Validate(new MyObject()); // Can't use YoursValidator to validate MyObject

        }
    }
}

要解决这个问题,我建议您尝试使用非泛型作为基类,以便您可以将验证器存储在字典中。看看以下是否适用于您的情况:

using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace GenericCollectionTest
{
    [TestFixture]
    public class GenericCollectionTest
    {

        interface IValiadtor
        {
            bool Validate(object item);
        }

        abstract class ValidatorBase<T> : IValidator<T>
        {
            public bool Validate(object item)
            {
                return Validate((T)item);
            }

            public abstract bool Validate(T item);
        }

        interface IValidator<T> : IValiadtor
        {
            //Error: "Parameter must be type-safe. Invalid variance..."
            bool Validate(T item);
        }

        class MyObject
        {
            public int TestValue { get; set; }
        }

        class YourObject
        {
            public int CheckValue { get; set; }
        }

        class MyValidator : ValidatorBase<MyObject>
        {
            public override bool Validate(MyObject item)
            {
                return (item).TestValue == 1;
            }
        }

        class YoursValdator : ValidatorBase<YourObject>
        {
            public override bool Validate(YourObject item)
            {
                return (item).CheckValue == 1;
            }
        }

        [Test]
        public void Test_That_Validator_Is_Working()
        {
            Dictionary<Type, IValiadtor> Validators = new Dictionary<Type, IValiadtor>();
            Validators.Add(typeof(MyObject), new MyValidator() );
            Validators.Add(typeof(YourObject), new YoursValdator());

            var someObject = new MyObject();
            someObject.TestValue = 1;
            Assert.That(Validators[someObject.GetType()].Validate(someObject));


        }
    }
}

【讨论】:

  • 是的,如果您注意到问题的底部,我建议您采用这种方法。我想知道除此之外是否还有其他方法。不过谢谢!
  • 由于类型安全问题,我不知道是否还有其他更好的方法。如果您阅读我的示例,就会很清楚为什么 .NET 设计人员不希望这样做。我已经多次使用这种基类技术并且一直运行良好。它强制我的团队使用接口进行编码,并允许您的架构或框架使用特定类型。您可能要尝试的最后一件事是返回验证委托(在这种情况下为谓词),但我不知道它在上述情况下如何帮助您。
  • 虽然我尽量避免走这条路,但这确实是正确的答案。事件虽然我提出了建议,但我给出了这个问题的答案。干杯!
猜你喜欢
  • 2012-09-29
  • 2020-11-20
  • 2011-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多