隐藏(使用“new”修饰符)接口方法声明的目的是什么?

时间:2010-08-13 12:18:53

标签: c# interface new-operator

可以在接口中将方法声明标记为“ new ”,但它是否具有任何“技术”意义,或者它只是一种明确声明声明不能覆盖前一个声明的方法?

例如:

interface II1
{
    new void F();
}

interface II2 : II1
{
    new void F();
}

有效(C#4.0编译器不会抱怨)但似乎与以下内容不同:

interface II1
{
    void F();
}

interface II2 : II1
{
    void F();
}

提前感谢任何信息。

编辑:你知道隐藏在界面中的场景会有用吗?

编辑:根据此链接:Is method hiding ever a good idea(感谢Scott), 最常见的情况似乎是协变返回类型的模拟。

7 个答案:

答案 0 :(得分:8)

第二个示例发出以下编译器警告:

  

'II2.F()'隐藏了继承的成员   'II1.F()'。使用新关键字if   隐藏的意图。

我想说使用new关键字的区别正是:显示意图。

答案 1 :(得分:6)

这两者非常不同。通过使用'new',您将创建一个新的继承链。这意味着II2的任何实现都需要实现F()的两个版本,并且您最终调用的实际版本将取决于引用的类型。

考虑以下三种实现:

    class A1 : II1
    {
        public void F()
        {
            // realizes II1.F()
        }
    }

    class A2 : II2
    {
        void II1.F()
        {
            // realizes II1.F()
        }

        void II2.F()
        {
            // realizes II2.F()
        }
    }

    class A3 : II2
    {
        public void F()
        {
            // realizes II1.F()
        }

        void II2.F()
        {
            // realizes II2.F()
        }
    }

如果您引用了A2,则在未先转换为F()II1的情况下,您将无法调用II2的任何一个版本。

A2 a2 = new A2();
a2.F(); // invalid as both are explicitly implemented
((II1) a2).F(); // calls the II1 implementation
((II2) a2).F(); // calls the II2 implementation

如果您引用A3,则可以直接调用II1版本,因为它是隐式实现:

A3 a3 = new A3();
a3.F(); // calls the II1 implementation
((II2) a3).F(); // calls the II2 implementation

答案 2 :(得分:4)

我知道一个很好的用途:你这样做来声明一个从另一个COM接口派生的COM接口。在magazine article中已经触及了它。

Fwiw,作者完全错误地认定了问题的根源,它与“遗产税”无关。 COM简单地利用了典型的C ++编译器实现多重继承的方式。每个基类都有一个v表,正是COM需要的。 CLR不会这样做,它不支持MI,并且只有一个v-table。接口方法合并到基类的v表中。

答案 3 :(得分:1)

new修饰符非常简单,它只是抑制隐藏方法时创建的编译器警告。如果在不隐藏其他方法的方法上使用,则会生成警告。

来自The C# Language Specification 3.0

10.3.4新修饰语 允许类成员声明声明与继承成员具有相同名称或签名的成员。发生这种情况时,称派生类成员隐藏基类成员。隐藏继承的成员不会被视为错误,但它确实会导致编译器发出警告。为了抑制警告,派生类成员的声明可以包含一个new修饰符,以指示派生成员是否要隐藏基本成员。本主题将在§3.7.1.2中进一步讨论。 如果新修饰符包含在不隐藏继承成员的声明中,则会发出对该效果的警告。删除新修饰符可以抑制此警告。

答案 4 :(得分:1)

Fredrik回答后的一个例子。我希望有一个接口,表示通过id获取实体的方法。然后我想要一个WCF服务和一些其他标准存储库能够用作该接口。为了使用WCF,我需要用属性来装饰我的界面。从一个意图的角度来看,我不想用WCF的东西装饰我的界面,因为它不会仅通过WCF使用,所以我需要使用new。这是代码:

public class Dog
{
    public int Id { get; set; }
}

public interface IGetById<TEntity>
{
    TEntity GetById(int id);
}

public interface IDogRepository : IGetById<Dog> { }

public class DogRepository : IDogRepository
{
    public Dog GetById(int id)
    {
        throw new NotImplementedException();
    }
}

[ServiceContract]
public interface IWcfService : IGetById<Dog>
{
    [OperationContract(Name="GetDogById")]
    new Dog GetById(int id);
}

答案 5 :(得分:1)

我几乎在每个界面都使用它。看这里:

interface ICreature
{
    /// <summary>
    ///     Creature's age
    /// </summary>
    /// <returns>
    ///     From 0 to int.Max
    /// </returns>
    int GetAge();
}

interface IHuman : ICreature
{
    /// <summary>
    ///     Human's age
    /// </summary>
    /// <returns>
    ///     From 0 to 999
    /// </returns>
    new int GetAge();
}

拥有较少要求但义务较大的继承成员是绝对正常的。 LSP没有被违反。但如果是的话,在哪里记录新合同?

答案 6 :(得分:1)

通读了这些答案后,他们很好地解释了新操作符 的作用,但是我对OP的这一部分问题没有明确的答案:

您知道隐藏在界面中会很有用的情况吗?

总而言之,所有这些都归结为可测试性和重用性。通过将接口分成较小的块,并坚持使用Interface Separation Principle,我们可以使类的用户最少地依赖无关的细节,并最大程度地分离,这为我们提供了更多的可重用性选项,并且使测试时间更短

通常,当我们需要以不可避免的方法冲突的方式分支接口类型层次结构时,新运算符会在此处起作用。所有这些听起来有点抽象,很难解释,所以我创建了一个我认为是一个最小的示例,我们希望将接口类型层次结构分为两个,同时有一个通用的共享方法。我将代码放在.NET小提琴上:

https://dotnetfiddle.net/kRQpoU

再次出现:

using System;

public class Program
{
    public static void Main()
    {
        //Simple usage
        var simpleCuboid = new MockCuboidSimple();
        var heightUser = new HeightUserEntangled();
        var volumeUser = new VolumeUserEntangled();
        Console.WriteLine("*** Simple Case ***");
        Console.WriteLine(heightUser.PrintHeight(simpleCuboid));
        Console.WriteLine(volumeUser.PrintVolume(simpleCuboid));

        //Smart usage - the same behaviour, but more testable behind the scenes!
        var smartCuboid = new MockCuboidSmart();
        var heightUserSmart = new HeightUserSmart();
        var volumeUserSmart = new VolumeUserSmart();
        Console.WriteLine("*** smart Case ***");
        Console.WriteLine(heightUserSmart.PrintHeight(smartCuboid));
        Console.WriteLine(volumeUserSmart.PrintVolume(smartCuboid));
    }
}

//Disentangled

class VolumeUserSmart
{
    public string PrintVolume(IThingWithVolume volumeThing)
    {
        return string.Format("Object {0} has volume {1}", volumeThing.Name, volumeThing.Volume);
    }       
}

class HeightUserSmart
{
    public string PrintHeight(IThingWithHeight heightThing)
    {
        return string.Format("Object {0} has height {1}", heightThing.Name, heightThing.Height);
    }       
}

class MockCuboidSmart : ICuboidSmart
{
    public string Name => "Mrs. Cuboid";
    public double Height => 3.333;
    public double Width => 31.23432;
    public double Length => 123.12;
    public double Volume => Height * Width * Length;
}

interface ICuboidSmart : IThingWithHeight, IThingWithVolume
{
    //Here's where we use new, to be explicit about our intentions
    new string Name {get;}
    double Width {get;}
    double Length {get;}
    //Potentially more methods here using external types over which we have no control - hard to mock up for testing
}

interface IThingWithHeight
{
    string Name {get;}
    double Height {get;}
}   

interface IThingWithVolume
{
    string Name {get;}
    double Volume {get;}
}

//Entangled

class VolumeUserEntangled
{
    public string PrintVolume(ICuboidSimple cuboid)
    {
        return string.Format("Object {0} has volume {1}", cuboid.Name, cuboid.Volume);
    }       
}

class HeightUserEntangled
{
    public string PrintHeight(ICuboidSimple cuboid)
    {
        return string.Format("Object {0} has height {1}", cuboid.Name, cuboid.Height);
    }       
}

class MockCuboidSimple : ICuboidSimple
{
    public string Name => "Mrs. Cuboid";
    public double Height => 3.333;
    public double Width => 31.23432;
    public double Length => 123.12;
    public double Volume => Height * Width * Length;
}

interface ICuboidSimple
{
    string Name {get;}
    double Height {get;}
    double Width {get;}
    double Length {get;}
    double Volume {get;}
    //Potentially more methods here using external types over which we have no control - hard to mock up for testing
}

请注意,VolumeUserSmartHeightUserSmart仅取决于它们所关心的ICuboidSmart接口的片段,即IThingWithHeightIThingWithVolume。这样,它们可以被最大程度地重用,例如除了长方体以外的其他形状,也可以更轻松地进行测试。在实践中,最后一点很关键。使用更少的方法来模拟接口要容易得多,尤其是如果主接口类型中的方法包含对我们无法控制的类型的引用。当然,总是可以通过一个模拟框架解决这个问题,但是我更喜欢保持代码的核心干净。

那么new关键字在哪里适合呢?好吧,由于VolumeUserSmartHeightUserSmart都需要访问Name属性,因此我们必须在IThingWithHeightIThingWithVolume中都声明它。因此,我们必须在子接口ICuboidSmart中重新声明它,否则我们将得到抱怨歧义性的编译器错误。在这种情况下,我们要做的是隐藏NameIThingWithHeight中定义的IThingWithVolume的两个版本,否则这两个版本会发生冲突。而且,就像其他答案所指出的那样,尽管我们在这里没有使用new,但是我们应该明确表明我们的隐藏意图。