【问题标题】:method hiding in c# with a valid example. why is it implemented in the framework? what is the Real world advantage?隐藏在 c# 中的方法和一个有效的例子。为什么要在框架中实现?现实世界的优势是什么?
【发布时间】:2010-11-14 16:43:53
【问题描述】:

谁能用一个有效的例子来解释方法隐藏在C#中的实际使用?

如果该方法是在派生类中使用new 关键字定义的,则它不能被覆盖。然后它与创建一个具有不同名称的新方法(基类中提到的方法除外)相同。

使用new 关键字有什么具体原因吗?

【问题讨论】:

  • “有效示例”是什么意思?一个简单语法正确的例子,还是一个展示方法隐藏最佳实践的例子?

标签: c# .net oop inheritance polymorphism


【解决方案1】:

我有时对 new 关键字的一个用途是用于并行继承树中的“可怜的 mans property covariance”。考虑这个例子:

public interface IDependency
{
}

public interface ConcreteDependency1 : IDependency
{
}

public class Base
{
  protected Base(IDependency dependency)
  {
    MyDependency = dependency;
  }

  protected IDependency MyDependency {get; private set;}
}

public class Derived1 : Base // Derived1 depends on ConcreteDependency1
{
  public Derived1(ConcreteDependency1 dependency)  : base(dependency) {}

  // the new keyword allows to define a property in the derived class
  // that casts the base type to the correct concrete type
  private new ConcreteDependency1 MyDependency {get {return (ConcreteDependency1)base.MyDependency;}}
}

继承树 Derived1 : Base 对 ConcreteDependency1 : IDependency 有一个“并行依赖”。在派生类中,我知道 MyDependency 的类型是 ConcreteDependency1,因此我可以使用 new 关键字从基类中隐藏属性 getter。

编辑:另请参阅 this blog post by Eric Lippert 以获得对新关键字的良好解释。

【讨论】:

  • 这真是个好家伙。我不知道这些错综复杂
【解决方案2】:

C#不仅支持方法覆盖,还支持方法隐藏。简单地说,如果一个方法没有覆盖派生方法,它就是隐藏它。必须使用 new 关键字声明隐藏方法。因此,第二个清单中的正确类定义是:

    using System;
    namespace Polymorphism
    {
        class A
        {
            public void Foo() { Console.WriteLine("A::Foo()"); }
        }

        class B : A
        {
            public new void Foo() { Console.WriteLine("B::Foo()"); }
        }

        class Test
        {
            static void Main(string[] args)
            {
                A a;
                B b;

                a = new A();
                b = new B();
                a.Foo();  // output --> "A::Foo()"
                b.Foo();  // output --> "B::Foo()"

                a = new B();
                a.Foo();  // output --> "A::Foo()"
            }
        }
    }

【讨论】:

  • 我想没有 new 关键字也可以达到同样的效果,唯一的问题是编译器会发出警告。否则有什么显着差异吗?
  • 不,没有区别,编译器会发出警告,如果你不注意警告,它会像你写新关键字一样工作
  • 这里重要的是引用的类型决定了调用的方法。
  • 我不知道为什么这被标记为答案。这只是一个例子,而 OP 询问 Why 而不是 How
【解决方案3】:

我认为 ArsenMkrt 的示例并不完全正确,至少它没有完全解释隐藏功能。通过从 B 类的 Foo 方法中删除 new 关键字,您仍然可以获得输出

A::Foo()
B::Foo()
A::Foo()

在像 Java 这样的编程语言中,所有方法都是“虚拟的”,您希望得到输出

A::Foo()
B::Foo()
B::Foo()

由于实例化,采用上述 ArsenMkrt 的代码

A a;
B b;

a = new A();
b = new B();
a.Foo(); 
b.Foo(); 

a = new B(); //<< Here
a.Foo();  

然而,在他的示例中,您仍然会得到“A::Foo()”,因为在 C# 中方法默认不是虚拟的,因此方法 B::Foo() 会自动隐藏 A 的 Foo()。要实现多态行为,必须改为如下编写:

class A
{
    public virtual void Foo() { Console.WriteLine("A::Foo()"); }
}

class B : A
{
    public override void Foo() { Console.WriteLine("B::Foo()"); }
}

现在是“new”关键字出现的地方。实际上,当您离开 B::Foo() 中的“覆盖”时,您将再次隐藏 A::Foo(),这意味着您不会覆盖它的默认值行为并且您没有实现多态性,即您再次获得“A::Foo()”作为输出。可以通过放置“新”关键字来实现相同的效果 - 这就是我不能 100% 理解你为什么必须放置它的地方 - 就像..

class A
{
    public virtual void Foo() { Console.WriteLine("A::Foo()"); }
}

class B : A
{
    public new void Foo() { Console.WriteLine("B::Foo()"); }
}

然后你又得到了输出

A::Foo()
B::Foo()
A::Foo()

【讨论】:

    【解决方案4】:

    一个稍微模糊的场景,其中方法隐藏是合适的,但由于缺乏任何干净的惯用方式来表达它,是基类将受保护的成员暴露给可继承的后代,但后代知道存在任何进一步的派生类都无法在不破坏事物的情况下使用该成员。许多类应该隐藏(但很少有)的方法的一个主要例子是MemberwiseClone()。在许多情况下,如果一个类有一个 private 成员,该成员期望它是对可变对象的唯一现存引用,那么派生类就不可能正确使用 MemberwiseClone()

    请注意,隐藏受保护成员构成对 LSP 的违反。与 LSP 的一致性要求在代码可能期望引用基类对象但接收到派生类对象的引用的地方,后一个对象应该像基类对象一样工作。然而,受保护的成员不在 LSP 的范围内,因为每个类型都绝对知道它的基类型。如果BarBoz 都派生自Foo,并且Boz 隐藏了Bar 需要的受保护成员,则Boz 隐藏该成员这一事实不会影响Bar,因为Bar的基本类型实例不可能是BozFoo 以外的任何东西。没有替代是可能的,因此可替代性是无关紧要的。

    【讨论】:

    • 我认为最好在你的答案中添加几行代码。相信我,它更好;)
    【解决方案5】:

    当您需要添加/修改基类方法上的属性时,您会使用它们。例如:

    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public new event EventHandler TextChanged
    {
        add { base.TextChanged += value; }
        remove { base.TextChanged -= value; }
    }
    

    基础Control 类有一个TextChanged 事件,但基础事件具有各种属性,以防止它出现在智能感知或设计器中。因为我使用的类广泛使用了Text 属性和TextChanged 事件,所以我希望TextChanged 事件在智能感知中显示并在属性窗口中可见。

    【讨论】:

    • 这是很好的用例之一
    【解决方案6】:

    这可能会有所帮助。

    class Base
    {
       public void F() {}
    }
    class Derived: Base
    {
       public void F() {}      // Warning, hiding an inherited name
    }
    

    在上面的例子中,在 Derived 中声明 F 会导致上报警告。隐藏继承的名称并不是一个错误,因为这会阻止基类的单独演变。例如,上述情况的发生可能是因为 Base 的较新版本引入了该类的早期版本中不存在的 F 方法。如果上述情况是错误的,那么对单独版本化的类库中的基类所做的任何更改都可能导致派生类变得无效。 来源:https://msdn.microsoft.com/en-us/library/aa691135%28v=vs.71%29.aspx

    【讨论】:

      【解决方案7】:

      它的少数用例之一是限制访问修饰符,因为您不能在覆盖的属性和方法中这样做。

      在我的例子中,我必须使用它来创建一个基类,我们称之为 M,它有许多可以公开获取和设置的属性,然后有一个派生的具体类,称为 X,它实际上有做一些事情并且需要防止其中一些属性被更改。

      例子:

      class M
      {
          public object A { get; set; }
      }
      
      class X : M
      {
          public new object A
          {
              get => base.A;
              private set => base.A = value;
          }
      }
      
      

      因此,当我使用 X 引用时,即使实际对象的类型为 M,我也无法在其上设置 A。

      您可能需要做的另一件事是返回不同的类型,很可能是派生类型。例如,我可以让我的 X.A 属性具有不同的类型。

      class X : M
      {
          public new CustomObject A
          {
              get => base.A as CustomObject;
              private set => base.A = value;
          }
      }
      

      换句话说,我的M 类是一个简单的 POCO 类,只表示纯数据,对序列化之类的东西很有用。我的X 类需要防止某些事情发生,比如在这种情况下设置A(顺便说一句,这可以通过投射对象来实现,但实际上不太可能是错误的)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-08
        • 1970-01-01
        • 2013-03-26
        • 2019-10-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多