2

我似乎对接口实现有一个基本的语法问题。基本上我有这个:

    public interface IMarkerInterface
    {       
    }

    public class ConcreteObject : IMarkerInterface
    {
    }

    public interface IDoStuffInterface
    {
        void DoStuff(IMarkerInterface obj);

        // also doesn't work
        // void DoStuff<T>(T obj) where T : IMarkerInterface;
    }

    public class ConcreteDoStuff : IDoStuffInterface
    {
        public void DoStuff(ConcreteObject c)
        {

        }
    }

在我看来,ConcreteObject实施IMarkerInterface,因此ConcreteDoStuff.DoStuff()应该实施IDoStuffInterface

但我得到一个编译错误"Error ConcreteDoStuff does not implement interface IDoStuffInterface.DoStuff()"

怎么会?

4

6 回答 6

5

您实现的方法需要具有与接口完全相同的签名。虽然所有“ConcreteObject”对象都是“IMarkerInterface”类型,但并非所有“IMarkerInterfaces”都是“ConcreteObject”。因此这两个签名是不等价的。接口必须能够向 CLR 保证该类型的任何对象都实现了有效的方法。

于 2012-08-14T22:23:15.433 回答
4

如果我给你任何实现的对象IDoStuffInterface,那么你希望能够以任何实现的对象DoStuff作为参数调用它的方法,对吗?但是,如果您想要什么是可能的,那么您可以以某种方式(作为界面用户不可见)仅使用 a调用,但不能使用任何其他实现. 因此,您想要的实际上是不可能的(也不是所希望的,因为它违反了Liskov Substitution Principle)。IMarkerInterfaceDoStuffConcreteObjectIMarkerInterface

但是,您可以编写一个显式的接口成员实现来实现接口的DoStuff(IMarkerInterface)方法,同时DoStuff(ConcreteObject)在它旁边提供一个普通的方法实现。接口的任何用户都只能看到并能够调用该DoStuff(IMarkerInterface)方法,但任何用户ConcreteDoStuff只能直接调用该DoStuff(ConcreteObject)方法。如果用户无论如何都想调用接口方法,则必须先将对象强制转换为IDoStuffInterface

public class ConcreteDoStuff : IDoStuffInterface
{
    // Explicit interface member implementation:
    // This method is not directly visible as a member of the class.
    void IDoStuffInterface.DoStuff(IMarkerInterface obj)
    {
        // Do something with 'obj', or throw an exception when
        // it has the wrong type. Delegate the call to the
        // other DoStuff method if you wish.
    }

    // Normal method, technically not related to the interface method:
    public void DoStuff(ConcreteObject c)
    {
        // Do your thing.
    }
}

编辑:

有趣的解决方法!你认为这是一种普遍的做法吗?或者解决方法更像是要避免的“黑客”?

显式实现接口的技术是众所周知的、很好理解的并且非常普遍。它不被认为是hack,也不必避免。在极少数情况下,甚至需要一个类实现两个接口,这两个接口都定义了一个同名的成员,但您想赋予它们不同的行为。

但是,在您限制允许的输入值或操作的所有情况下,您都违反了 Liskov 替换原则 (LSP)。在我的示例中,您会将IMarkerInterface obj参数限制为ConcreteObject对象。其他示例包括: a在添加对象Collection<T>时引发异常nullIComparable.CompareTo当参数类型错误时抛出错误的实现;或在调用其方法ReadOnlyCollection时引发异常的a。Add

虽然不应该违反 LSP(因此代码变得更加(重)可用和可测试),但它经常被违反,即使在 .NET Framework 本身中也是如此。在某些情况下不违反它可能会很麻烦,导致代码难以阅读,甚至可能是不可能的(例如,由于.NET Framework 的类和接口的限制)。

正如我所说,相同的“解决方法”适用于IComparable. 例如,要实现一个只能与相同类型的其他对象进行比较的类:

public class BeanBag : IComparable, IComparable<BeanBag>
{
    private int beanCount;

    // Explicit interface member implementation:
    int IComparable.CompareTo(object other)
    {
        if (!(other is BeanBag))
            throw new ArgumentException("Wrong type!");
        // Calls the normal CompareTo() method.
        return CompareTo((BeanBag)other);
    }

    // Normal CompareTo method:
    public int CompareTo(BeanBag other)
    {
        if (other == null) return 1;
        return this.beanCount.CompareTo(other.beanCount);
    }
}
于 2012-08-14T22:27:48.017 回答
2

您的实现类ConcreteDoStuff正在尝试更改合同。单方面。

于 2012-08-14T22:22:07.463 回答
2

您需要实现具有匹配签名的方法:

public class ConcreteDoStuff : IDoStuffInterface
{
    public void DoStuff(IMarkerInterface c) // not ConcreteObject c
    {

    }
}

请注意,您仍然可以使用 ConcreteObject 实例调用 ConcreteDoStuff.DoStuff :

 var concrete = new ConcreteObject()
 new ConcreteDoStuff().DoStuff(concrete);

如果它对您更有效,您也可以这样做(接口的显式实现):

public class ConcreteDoStuff : IDoStuffInterface
{
    public void DoStuff(ConcreteObject c)
    {

    }

    void IDoStuffInterface.DoStuff(IMarkerInterface c)
    {
       // some implementation preferably related to
       // DoStuff(ConcreteObject) i.e.: 
       DoStuff(c as ConcreteObject); 
    }
}
于 2012-08-14T22:23:10.790 回答
2

通过只接受ConcreteObject你的实现,ConcreteDoStuff.DoStuff()你限制了可以传入的参数类型。

如果您要创建另一个也实现了的类IMarkerInterface

public class ConcreteObject2 : IMarkerInterface
{
}

它应该能够传递给任何DoStuff()实现,但ConcreteObject2不是ConcreteObject根据定义,因此合同将被违反,正如其他答案所表明的那样。

于 2012-08-14T22:24:52.953 回答
1

你最好使用通用接口:

public interface IMarkerInterface
{       
}

public class ConcreteObject : IMarkerInterface
{
}

public interface IDoStuffInterface<T>
{
    void DoStuff(T obj);
}

public class ConcreteDoStuff : IDoStuffInterface<ConcreteObject>
{
    public void DoStuff(ConcreteObject c)
    {

    }
}
于 2012-08-14T22:24:05.703 回答