【问题标题】:"Constraints for explicit interface implementation..."“显式接口实现的约束……”
【发布时间】:2008-11-12 12:40:41
【问题描述】:

我不明白为什么以下方法行不通,有什么想法吗? 公共接口 IFieldSimpleItem { }

public interface IFieldNormalItem : IFieldSimpleItem
{ }

public class Person
{
    public virtual T Create<T>()
        where T : IFieldSimpleItem
    {
        return default(T);
    }
}

public class Bose : Person
{
    public override T Create<T>()
        where T : IFieldNormalItem //This is where the error is
    {
        return default(T);
    } 
}

我这样做的原因是,如果开发人员从 Bose 继承,Bose 依赖于创建的实例至少是 IFieldNormalItem。而下面只依赖它是 IFieldSimpleItem 但上面应该强制它至少是 IFieldNormalItem。

public class Person
{
    public virtual IFieldSimpleItem Create() 
    {
        return null;
    }
}

public class Bose : Person
{
    public override IFieldSimpleItem Create()  
    {
        return null;
    } 
}

干杯 安东尼

【问题讨论】:

    标签: c# .net generics


    【解决方案1】:

    我很确定您在使用编译器和泛型来节省一些运行时检查方面并不走运。你不能覆盖不存在的东西,你不能对相同的方法有不同的返回类型。

    我不能说我完全理解你的动机,但它具有技术价值。

    我的第一次尝试是使用具有非虚拟公共接口的基类,然后使用另一个受保护的虚拟方法 CheckCreatedType,这将允许链中的任何内容在调用基类 Create 之前检查类型。

    public class A
    {
        public IFieldSimpleItem Create()
        {
            IFieldSimpleItem created = InternalCreate();
            CheckCreatedType(created);
            return created;
        }
    
        protected virtual IFieldSimpleItem InternalCreate()
        {
            return new SimpleImpl();
        }
        protected virtual void CheckCreatedType(IFieldSimpleItem item)
        { 
            // base class doesn't care. compiler guarantees IFieldSimpleItem
        }
    }
    public class B : A
    {
        protected override IFieldSimpleItem InternalCreate()
        {
            // does not call base class.
            return new NormalImpl();
        }
        protected override void CheckCreatedType(IFieldSimpleItem item)
        {
            base.CheckCreatedType(item);
            if (!(item is IFieldNormalItem))
                throw new Exception("I need a normal item.");
    
        }
    }
    

    以下内容坚持基类的运行时检查。无法解决的问题是您仍然必须依赖被调用的基类方法。行为不端的子类可以通过不调用 base.CheckCreatedType(item) 来破坏所有检查。

    替代方法是硬编码基类中所有子类的所有检查(不好),或者以其他方式外部化检查。

    尝试 2:(子)类注册他们需要的检查。

    public class A
    {
        public IFieldSimpleItem Create()
        {
            IFieldSimpleItem created = InternalCreate();
            CheckCreatedType(created);
            return created;
        }
    
        protected virtual IFieldSimpleItem InternalCreate()
        {
            return new SimpleImpl();
        }
    
        private void CheckCreatedType(IFieldSimpleItem item)
        {
            Type inspect = this.GetType();
            bool keepgoing = true;
            while (keepgoing)
            {
                string name = inspect.FullName;
                if (CheckDelegateMethods.ContainsKey(name))
                {
                    var checkDelegate = CheckDelegateMethods[name];
                    if (!checkDelegate(item))
                        throw new Exception("failed check");
                }
                if (inspect == typeof(A))
                {
                    keepgoing = false;
                }
                else
                {
                    inspect = inspect.BaseType;
                }
            }
        }
    
        private static Dictionary<string,Func<IFieldSimpleItem,bool>> CheckDelegateMethods = new Dictionary<string,Func<IFieldSimpleItem,bool>>();
        protected static void RegisterCheckOnType(string name, Func<IFieldSimpleItem,bool> checkMethod )
        {
            CheckDelegateMethods.Add(name, checkMethod);
        }
    }
    public class B : A
    {
        static B()
        {
            RegisterCheckOnType(typeof(B).FullName, o => o is IFieldNormalItem);
        }
    
        protected override IFieldSimpleItem InternalCreate()
        {
            // does not call base class.
            return new NormalImpl();
        }
    }
    

    检查是由子类在基类中注册一个要调用的委托来完成的,但基类不需要预先知道所有规则。还要注意,它仍然是非虚拟公共接口,它允许基类在返回结果之前检查结果。

    我假设这是您要捕获的开发人员错误。如果适用,您可以使用System.Diagnostics.Conditional("DEBUG")] 修饰运行时检查方法,允许发布版本跳过检查。

    我对泛型的了解并不完美,所以这可能是不必要的。然而,这里的检查不必只针对类型:这可以适用于其他用途。例如Register.. 中传递的委托不必只检查引用是否为特定类型'

    * 请注意,在上面写的类型名称上创建字典可能不太好;为了说明所使用的机制,这个工作有点简单。

    【讨论】:

    • 谢谢,尽管这个检查是在运行时完成的,但我认为这是最接近的。干杯
    【解决方案2】:

    这是不允许的,因为它违反了 Liskov 替换原则。

    假设你有另一个界面:

    public interface IFieldSuperItem : IFieldSimpleItem
    

    你可以这样做

    Person p = new Boss();
    p.Create<IFieldSuperItem>();
    

    第二行的调用,虽然与 Create in Person 的定义兼容,但显然与 Boss 中的定义不兼容(仅适用于 IFieldNormalItem 及其子类)。

    【讨论】:

      【解决方案3】:

      我认为问题在于您覆盖了先前定义的方法。因此,您有效地尝试更改方法的定义,这是不允许的。您唯一的选择是创建一个新方法,例如

      public class Bose : Person
      {
          public virtual T CreateNormal<T>()
              where T : IFieldNormalItem //This is where the error is
          {
              return default(T);
          } 
      }
      

      或要求 Person 类上的普通字段,或动态进行验证。

      【讨论】:

      • 我本以为没问题,因为我让定义更强而不是更弱。
      【解决方案4】:

      您似乎无法更改方法的定义,但您可以让您的类通用而不是创建方法吗?

      public class Person<T> where T : IFieldSimpleItem
      {
          public virtual T Create()
          {
              return default(T);
          }
      }
      
      public class Bose<T> : Person<T> where T : IFieldNormalItem
      {
          public override T Create()
          {
              return default(T);
          } 
      }
      

      【讨论】:

      • 很遗憾我做不到。我刚刚花了很长时间从我的代码中删除泛型的样式。在这种情况下,我无法将整个类设为泛型(即 Person),因为我必须进行一些强制转换。
      【解决方案5】:

      更改通用约束会更改方法签名,如果您要覆盖虚拟,则不允许这样做。

      我认为您可能需要将 Create 方法拆分为一个单独的类:

      public interface IFieldSimpleItem { }
      
      public interface IFieldNormalItem : IFieldSimpleItem{ }
      
      public interface IFieldCreator<TField, TPerson> where TField : IFieldSimpleItem where TPerson : Person
      {
          TField Create(TPerson person);
      }
      
      public class Person
      {
      }
      
      public class Bose : Person
      {
      }
      
      public class PersonFieldCreator : IFieldCreator<IFieldSimpleItem, Person> 
      {
          public IFieldSimpleItem Create(Person person) { return null; }
      }
      
      public class BoseFieldCreator : IFieldCreator<IFieldNormalItem, Bose>
      {
          public IFieldNormalItem Create(Bose person) { return null; }
      }
      

      【讨论】:

      • 问题是@vdhant 有一个约束,他/她希望确保 Bose 的子类创建一个至少是 IFieldNormalItem 的类型
      • 在本例中,BoseFieldCreator 适用于从 Bose 派生的类。
      【解决方案6】:

      最简单的例子是这破坏了多态性。如果您有一个 Person 集合,其中一个或多个项目属于 Bose 类型,那么它会在遇到 Bose 时立即崩溃。

      Person[] people;
      [...initialize this somewhere...]
      
      foreach(Person p in people)
        p.Create<IFieldSimpleItem>();
      

      【讨论】:

        【解决方案7】:

        这个呢:

        public interface IFieldNormalItem : IFieldSimpleItem
        { }
        
        public class Person<T> where T : IFieldSimpleItem
        {
            public virtual T Create()
            {
                return default(T);
            }
        }
        

        现在您可以拥有Person&lt;IFieldSimpleItem&gt;(对应于Person)或Person&lt;IFieldNormalItem&gt;(对应于Bose)。

        【讨论】:

        • 让类泛型,创建泛型方法对吗?
        • 我认为这是“这是一个拥有简单领域的人”与“这是一个拥有普通领域的人”的案例。在这种情况下,您实际上是在区分人并使类通用化是可以接受的。
        • 不幸的是 yapiskan 是对的。在这种情况下,我无法将整个类设为泛型(即 Person),因为我必须进行一些强制转换。
        【解决方案8】:

        下面的代码足以覆盖。类型 T 已在基类 Person 中指示需要由 IFieldSimpleItem 实现。

        public class Bose : Person
        {
            public override T Create<T>()
                // where T : IFieldNormalItem // You don't need this line.
            {
                return default(T);
            } 
        }
        

        编辑: 我完全把问题弄错了,所以上面的代码不能解决这个问题。你唯一需要做的就是;不要通过“覆盖”而是“虚拟”来覆盖 Create 方法。

        public class Bose : Person
        {
            public virtual T Create<T>()
                where T : IFieldNormalItem
            {
                return default(T);
            } 
        }
        

        【讨论】:

        • 但这只会强制创建至少使用 IFieldSimpleItem。问题是,如果某些东西从 Bose 继承并用返回 IFieldSimpleItem 实现的东西覆盖创建,它将出错,因为 bose 需要它至少是 IFieldNormalItem。
        猜你喜欢
        • 2011-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-16
        相关资源
        最近更新 更多