“显式接口实现的约束......”

时间:2008-11-12 12:40:42

标签: c# .net generics

我无法弄清楚为什么以下不会工作,任何想法?     公共接口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;
    } 
}

干杯 安东尼

8 个答案:

答案 0 :(得分:3)

对于使用编译器和泛型来节省运行时检查,我很确定你运气不好。您不能覆盖尚不存在的内容,并且您不能对相同的方法使用不同的返回类型。

我不能说我完全理解你的动机,但它有技术上的优点。

我的第一次尝试是使用具有非虚拟公共接口的基类,然后使用另一个受保护的虚拟方法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..的代表不必仅检查引用是否为特定类型'

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

答案 1 :(得分:2)

这是不允许的,因为它违反了利斯科夫替代原则。

假设你有另一个界面:

public interface IFieldSuperItem : IFieldSimpleItem

然后你可以这样做

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

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

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

我认为您可能需要将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; }
}

答案 5 :(得分:0)

这个怎么样:

public interface IFieldNormalItem : IFieldSimpleItem
{ }

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

现在,您可以Person<IFieldSimpleItem>(对应Person)或Person<IFieldNormalItem>(对应Bose)。

答案 6 :(得分:0)

以下代码足以覆盖。类型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);
    } 
}

答案 7 :(得分:0)

最简单的例子就是打破了多态性。如果你有一个Person集合,其中一个或多个这些项目属于Bose类型,那么一旦它击中Bose就会崩溃。

Person[] people;
[...initialize this somewhere...]

foreach(Person p in people)
  p.Create<IFieldSimpleItem>();