在方法签名中的new关键字

124

在执行重构时,我最终创建了一个如下所示的方法。由于简化起见,数据类型已更改。

之前我有一个类似以下的赋值语句:

MyObject myVar = new MyObject();

这段代码是意外地重构成了这样:

private static new MyObject CreateSomething()
{
  return new MyObject{"Something New"};
}

这是由于我在剪切/粘贴时出现了错误,但 private static new 中的 new 关键字是有效的并且可以编译。

问题:方法签名中的 new 关键字表示什么?我猜这是在 C# 3.0 中引入的一些东西吗?

override 有何不同?


5
关于方法隐藏的优越性的一些注记:http://blogs.msdn.com/ericlippert/archive/2008/05/21/method-hiding-apologia.aspx - Eric Lippert
2
@Eric.. 很棒的帖子。我从未想过以那种方式隐藏方法,就像是,对象确实是一件事,但我们现在将其呈现为其他东西,因此我们希望它表现出我们所呈现的东西的行为。聪明... - BFree
1
将来可能会有重复的问题,我已经尝试详细回答了:在C#中使用new关键字 - Ken Kin
1
在方法(或其他类型成员)上使用new作为修饰符并不是“C# 3.0中引入的新功能”。自C#的第一个版本以来,它就一直存在。 - Jeppe Stig Nielsen
1
在 Stack Overflow 中已经有大约四个重复的问题了。我认为,这个链接会给你最好的理解: https://dev59.com/p3A75IYBdhLWcg3wxcDV。这是由 @EricLippert 回答的。 - Raikol Amaro
显示剩余2条评论
9个回答

114

MSDN中的new修饰符参考

这里还有一个我在网络上找到的例子,来自一个对微软的最有价值专家(原始链接):

public class A
{
   public virtual void One();
   public void Two();
}

public class B : A
{
   public override void One();
   public new void Two();
}

B b = new B();
A a = b as A;

a.One(); // Calls implementation in B
a.Two(); // Calls implementation in A
b.One(); // Calls implementation in B
b.Two(); // Calls implementation in B

override只能在非常特殊的情况下使用。根据MSDN的说明:

你不能覆盖非虚拟或静态方法。被覆盖的基本方法必须是虚拟、抽象或重写的。

因此,需要使用new关键字来允许你“覆盖”非virtualstatic方法。


4
我刚才运行了你的示例,即使你没有指定“new”,它也会覆盖原有的内容,对吗? - Michael Sync
2
@MichaelSync 没错,那么为什么我们需要提到新的关键字呢? - ZoomIn
4
根据相关文档,即使不使用new修饰符也可以隐藏成员,但此时编译器会发出警告。因此,使用该关键字可以明确你的意图,从而避免发出警告。 - Jim Counts
3
完全不需要-1 -> 这种行为将保持不变。对于新手来说,“new”到底是什么非常令人困惑,但我认为它只是为了可读性而存在——编译器只是在警告你,当你调用与基类同名的派生类方法时,你将得不到你可能认为的基类方法……这很奇怪……纯粹是为了可读性? - Don Cheadle
1
为了完整起见:如果A类和B类都实现了接口IMyInterface,则派生类上的实现将被调用。因此,IMyInterface c = new B()将调用B类的实现。如果只有一个类实现了接口,则将调用实现它的类的方法。 - Nullius
显示剩余2条评论

66
不,实际上它并不是“新的”(请原谅双关语)。它基本上用于“隐藏”一个方法。例如:
public class Base
{
   public virtual void Method(){}
}

public class Derived : Base
{
   public new void Method(){}
}

如果您然后这样做:

Base b = new Derived();
b.Method();

在这种情况下,调用的是基类中的方法而不是派生类中的方法。

更多信息请参见:http://www.akadia.com/services/dotnet_polymorphism.html

关于您的编辑:在我给出的示例中,如果您使用“override”而不是使用“new”,则在调用b.Method();时会由于多态性而调用派生类的Method。


@michael 这个方法仍然是虚拟的。 - Rune FS
2
如果省略了“override”这个词,那么默认行为就是“new”,例如方法隐藏。因此,您省略“new”这个词并没有任何影响。 - BFree
1
是的。我也这样认为。不确定为什么C#添加了"new"关键字...只是为了让警告消失.. https://dev59.com/vl7Va4cB1Zd3GeqPIUcb - Michael Sync
为了完整起见:如果基类和派生类都实现了接口 IMyInterface,则将调用派生类上的实现。因此,IMyInterface i = new Derived() 将调用派生类的实现。如果只有一个类实现了接口,则将调用实现它的类的方法。 - Nullius
@MichaelSync 我认为他们使用 "new" 关键字,因为虽然这个方法与基类中的方法同名,但它是一个全新的(非继承)方法,与基类中的方法毫无关系。 它就像衍生类特定的任何其他方法一样,但由于它的名称与基类中的名称匹配,我们必须明确地说明我们没有忘记关键字 "override" 并且我们选择的方法名称并不是因为覆盖。 - Almir
显示剩余2条评论

25

正如其他人所解释的,它用于隐藏现有方法。如果要重写父类中未定义为虚拟的方法,则此功能非常有用。

请记住,创建“新”成员不具备多态性。如果将对象转换为基本类型,则不会使用派生类型的成员。

如果您有一个基类:

public class BaseClass
{
    public void DoSomething() { }
}

接着是派生类:

public class DerivedType : BaseClass
{
    public new void DoSomething() {}

}

如果您声明了一个名为DerivedType的类型并进行转换,方法DoSomething()不会具有多态性,它将调用基类的方法而不是派生类的方法。
BaseClass t = new DerivedType();
t.DoSomething();// Calls the "DoSomething()" method of the base class.

1
即使您从DerivedType中删除“new”,它仍将调用基类中的方法。 - Michael Sync
2
我认为你的第二段完美地解决了整个话题。当处理来自“基类引用”的调用时,“new”不是多态的...也就是说,当你进行调用时,你会得到你所指定的内容。而“override”是多态的...也就是说,你会得到类层次结构所指定的内容。 - Jonathon Reinhart
这个东西为什么定义得如此模糊?对于新手来说非常令人沮丧。不,这与多态无关——它的行为与在方法签名中没有使用“override”完全相同。 - Don Cheadle
什么被隐藏了?肯定不是new将基类型从派生类型中隐藏,因为你总是可以使用base.<type>表示法访问基类型。 - thatWiseGuy
@thatWiseGuy 在这里的“hidden”是指在您的代码中使用派生类时,基本方法不会被调用。但是,您仍然可以使用base.<method>在内部调用它,并且如果您在代码中将其转换为基本类型,则也可以在外部调用它。从技术上讲,更准确的说法是“覆盖”基本方法,而不是“隐藏”它。 - Dan Herbert

9
文档中:

如果派生类中的方法前面带有new关键字,则该方法被定义为与基类中的方法无关。

在实践中,这意味着:

如果你从另一个类继承,并且你有一个与之具有相同签名的方法,你可以将其定义为new,以使其独立于父类。这意味着如果你引用了“父”类,那么将执行该实现;如果你引用了子类,那么将执行该实现。

个人而言,我尽量避免使用new关键字,因为通常意味着我的类层次结构有问题,但有时它也是有用的。一个应用场景是版本控制和向后兼容性。

Microsoft参考资料中有很多信息。


1
这种行为发生与new是否存在无关,而是语法/可读性问题。 - Don Cheadle
那句话对我来说很有意义。如果有人能验证一下这个信息就太好了。链接已经失效了。 - carloswm85

7
长话短说——它不是必需的,不改变任何行为,纯粹是为了可读性。
这就是为什么在VS中你会看到一个小波浪线,但你的代码将编译并完美地运行和预期。
我们不得不想知道当所有意思只是开发人员承认"是的,我知道我隐藏了一个基本方法,是的,我知道我没有做任何与虚拟或重写(多态)相关的事情——我真的想创建自己的方法"时,是否真的值得创建new关键字。
对我来说有点奇怪,但可能只是因为我来自于Java的背景,而在Java中,方法默认是虚拟的,除非被final指定。在C#中,方法默认是final/concrete的,除非被virtual指定。

4

它意味着该方法通过继承自基类的同名方法进行替换。在您的情况下,您可能没有一个在基类中具有该名称的方法,这意味着新关键字完全是多余的。


1

来自MSDN

使用new修饰符显式地隐藏从基类继承的成员。要隐藏继承的成员,请在派生类中使用相同的名称声明它,并使用new修饰符进行修改。


0

注意这个陷阱。
你在接口中定义了一个方法,它被实现在基类中。然后你创建了一个派生类来隐藏接口的方法,但没有明确声明该派生类作为接口的实现。如果你通过对接口的引用调用该方法,将会调用基类的方法。 但是如果你的派生类明确实现了该接口,则无论使用哪种类型的引用,都将调用其方法。

interface IMethodToHide
{
    string MethodToHide();
}

class BaseWithMethodToHide : IMethodToHide
{
    public string MethodToHide()
    {
        return "BaseWithMethodToHide";
    }
}

class DerivedNotImplementingInterface   : BaseWithMethodToHide
{
    new public string MethodToHide()
    {
        return "DerivedNotImplementingInterface";
    }
}

class DerivedImplementingInterface : BaseWithMethodToHide, IMethodToHide
{
    new public string MethodToHide()
    {
        return "DerivedImplementingInterface";
    }
}

class Program
{
    static void Main()
    {
        var oNoI = new DerivedNotImplementingInterface();
        IMethodToHide ioNoI = new DerivedNotImplementingInterface();

        Console.WriteLine("reference to the object type DerivedNotImplementingInterface calls the method in the class " 
            + oNoI.MethodToHide());
        // calls DerivedNotImplementingInterface.MethodToHide()
        Console.WriteLine("reference to a DerivedNotImplementingInterface object via the interfce IMethodToHide calls the method in the class " 
            + ioNoI.MethodToHide());
        // calls BaseWithMethodToHide.MethodToHide()
        Console.ReadLine();

        var oI = new DerivedImplementingInterface();
        IMethodToHide ioI = new DerivedImplementingInterface();

        Console.WriteLine("reference to the object type DerivedImplementingInterface calls the method in the class " 
            + oI.MethodToHide());
        // calls DerivedImplementingInterface.MethodToHide()
        Console.WriteLine("reference to a DerivedImplementingInterface object via the interfce IMethodToHide calls the method in the class " 
            + ioI.MethodToHide());
        // calls DerivedImplementingInterface.MethodToHide()
        Console.ReadLine();

    }
}

0

顺便说一下,我已经写了相当长时间的C#代码了,唯一需要使用new关键字的情况是以下面向对象层次结构的场景:

public abstract class Animal
{
    protected Animal(Animal mother, Animal father)
    {
        Mother = mother;
        Father = father;
    }

    public Animal Mother { get; }
    public Animal Father { get; }
}

public class Dog : Animal
{
    public Dog(Dog mother, Dog father)
        : base(mother, father)
    {
    }

    public new Dog Mother => (Dog)base.Mother;
    public new Dog Father => (Dog)base.Father;
}

几点说明:

  • 没有违反任何面向对象的规则,基类仍然可以正常工作。
  • 属性故意没有标记为virtual,因为它们不需要。
  • 实际上,由于新关键字,我们强制执行更多的面向对象规则:狗不能有任何动物作为父母。它必须是一只狗。
  • 使用new关键字是因为我们无法通过改变其返回类型来“覆盖”属性(或方法)。new是拥有良好面向对象层次结构的唯一方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接