【问题标题】:Why generic + interface + shadowing methods cannot work together?为什么泛型 + 接口 + 阴影方法不能一起工作?
【发布时间】:2014-07-21 13:48:14
【问题描述】:

在 C# 中,混合泛型、接口和影子方法(C# 中的 new 关键字)似乎并不像预期的那样工作(在我看来)。 标记为新(阴影)的显式方法表现为覆盖方法!

这是一个用例:

创建一个C# winforms项目,去掉生成的Form1.cs 并用此代码替换 Program.cs:

using System;
using System.Text;
using System.Windows.Forms;

namespace GenericWithShadowedMethImplInterface
{

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.Run(new ChildForm());
        }
    }

    public interface IStepByStep_UI_Initialization
    {
        void FillControls();
        void DefineBindings();
        // etc...
    }

    public class BaseForm : Form, IStepByStep_UI_Initialization
    {
        public StringBuilder Log = new StringBuilder();

        public BaseForm()
        {
            this.InstallSmartLoading();
            Shown += (sender, args) => MessageBox.Show(Log.ToString());
        }

        public void FillControls()
        {
            Log.AppendLine("BaseObject.FillControls");
        }

        public void DefineBindings()
        {
            Log.AppendLine("BaseObject.DefineBinding");
        }
    }

    public class ChildForm : BaseForm, IStepByStep_UI_Initialization
    {
        public ChildForm()
        {
            this.InstallSmartLoading();
        }
        // Shadowing is really what i want
        public new void FillControls()
        {
            Log.AppendLine("ChildObject.FillControls");
        }

        // Shadowing is really what i want
        public new void DefineBindings()
        {
            Log.AppendLine("ChildObject.DefineBinding");
        }
    }

    public static class StepByStepInitializer
    {
        public static void InstallSmartLoading<TForm>(this TForm form)
            where TForm : Form, IStepByStep_UI_Initialization
        {
            // i Use lambda to keep knowing what form type really is (BaseForm or ChildForm)
            form.Load += (_, __) =>
            {
                // I would expect the these two lines of code here...
                // Why these calls are treated as polymorphic calls ?
                form.FillControls(); // always call ChildForm.FillControls even if typeof(TForm) == typeof(BaseForm)
                form.DefineBindings();

                // ... behaves likes this (not generic) code :
                //if (typeof(TForm) == typeof(BaseForm))
                //{
                //    (form as BaseForm).FillControls();
                //    (form as BaseForm).DefineBindings();
                //}
                //else if (typeof(TForm) == typeof(ChildForm))
                //{
                //    (form as ChildForm).FillControls();
                //    (form as ChildForm).DefineBindings();
                //}
            };
        }

    }
}

运行它...

你应该看到:

ChildObject.FillControls ChildObject.DefineBindings ChildObject.FillControls ChildObject.DefineBindings

现在,如果您在 InstallSmartLoading 中注释两行代码并取消注释其他代码行,然后再次运行该项目,您应该会看到:

BaseObject.FillControls BaseObject.DefineBindings ChildObject.FillControls ChildObject.DefineBindings

所以我的问题很简单:为什么您注释的两行代码的行为与您未注释的代码不同?在初始化 ChildForm 之前,我需要完全初始化我的 baseBaseForm。这是泛型的限制吗? :((( 有解决办法吗

  • TForm 表示真正的 Form 类型...
  • 方法未标记为虚拟/覆盖,我明确使用 new 关键字
  • 编译器不应该显示警告?

提前谢谢...

【问题讨论】:

  • 您是否有 C++ 背景?在 C++ 中,当基类构造函数运行时,对象的“类型”就是该基类类型。而在 C# 中,对象在构造/销毁期间不会更改其类型 - 它们始终是最终类型。
  • “我需要在初始化 ChildForm 之前完全初始化我的基本 BaseForm。这是泛型的限制吗?:(((有解决方法吗?”只需在ChildForm.FillControls() 中调用base.FillControls();
  • @Damien_The_Unbeliever 是的,我的思想是建立在 C++ 之上的。尽管如此,我使用 C# 已有 6-7 年了,我确实喜欢它。这是我第一次发现代码不是很直观。
  • @Greg 因为在我们的项目中,我们想要定义一种标准的方式来初始化表单。目前我们有 7-8 个步骤:DefineBindings、FillControls、CustomizeUI、WireEvents 等……所以当我(或任何开发人员)想要做一个新表单时,规则很简单:实现这个接口并在 Constructor 中调用 Install。例如,初始化程序处理 Visual Studio 在表单加载事件中吞下异常的事实......此外,如果其他人想要调试或改进表单,即使他从未开发过此表单,他现在也将在哪里查看。
  • 因此,当我(或任何开发人员)想要创建一个新表单时,规则很简单:实现此接口并在构造函数中调用 Install。 您发布的用于解决此问题的实现问题似乎令人难以置信的复杂,也许我错过了一些东西。如果这就是它的全部目的,我建议您将 BaseForm 抽象化(这不是绝对必要的)并按顺序在 BaseForm 上的某些私有方法中调用适当的方法。您可以以 WPF 窗口生命周期为例 codeproject.com/Articles/403418/…

标签: c# generics interface shadow


【解决方案1】:

你有约束

where TForm : IStepByStep_UI_Initialization

(约束的其他部分现在不相关),那么你有一个变量(实际参数)

TForm form

你做的:

form.FillControls(); // always call ChildForm.FillControls even if typeof(TForm) == typeof(BaseForm)

(您对问题的评论)。

之所以能编译,是因为你有这个接口约束。

注意ChildForm重新实现接口IStepByStep_UI_Initialization已经被BaseForm实现了。

所以你的调用真的相当于

((IStepByStep_UI_Initialization)form).FillControls();

并且TForm 是什么变得无关紧要。重要的是实例如何实现接口。

尝试以下方法:

  • 停止重新实现接口,只在BaseForm实现,
  • 将接口约束更改为基类约束where TForm : BaseForm

加深你的理解。


这是一个没有泛型、没有委托和事件、没有 Win Forms 的相关示例:

interface ICanTalk
{
  void Talk();
}

class Animal : ICanTalk
{
  public void Talk()
  {
    Console.WriteLine("I am animal");
  }
}
class Dog : Animal, ICanTalk // note: re-implements
{
  public new void Talk() // note: method hiding "new" is always evil
  {
    Console.WriteLine("Wroof!");
  }
}

static class Test
{
  internal static void Run()
  {
    object x = new Dog();

    ((Animal)x).Talk();   // I am animal
    ((Dog)x).Talk();      // Wroof!
    ((ICanTalk)x).Talk(); // Wroof!
  }
}

请注意,将上面的object x = ... 更改为Animal xDog x 是不相关的,这意味着它不会改变调用的方法。

【讨论】:

  • 实际上,在您的代码中,编译器将对象视为动物/狗。因此,当它调用该方法时,它只知道 Animal.Talk 或 Dog.Talk(因为方法不是虚拟的)。在我的例子中,我用泛型对编译器说:form 是一个实现 IStepByStep...的类型 TForm 的对象。IStepByStep... 只是用于约束 TForm,而不是替换它。所以泛型方法应该使用TForm,而不是接口。我认为这是因为我来自 C++。我认为这很奇怪,因为如果我使用泛型方法或接口,代码的含义没有区别。
  • 在我看来,奇怪的是void Foo&lt;TBar&gt;(TBar bar) where TBar : IBaz) {... } 等同于void Foo(IBaz bar) {... }
  • @MickaëlL 在编译时选择要绑定的方法(这不是dynamic 类型)之前知道TForm 是什么。无论您是否包含 new 或修饰符,具有相同名称和签名的两个方法都不是“相关的”。实际上,不鼓励隐藏这样的方法并导致混乱。你说得对,C# 泛型不像 C++ 模板。
猜你喜欢
  • 2022-09-24
  • 2016-03-22
  • 2018-02-10
  • 1970-01-01
  • 2019-12-29
  • 1970-01-01
  • 2013-09-20
  • 2011-09-08
  • 1970-01-01
相关资源
最近更新 更多