【问题标题】:How to "override" extension methods in .NET?如何“覆盖”.NET 中的扩展方法?
【发布时间】:2013-04-22 06:15:25
【问题描述】:

所以我有一个对象层次结构来在 asp.net mvc 中生成 ui 控件并尝试实现流畅的 api。我做了一些虚拟课程来专注于当前的问题。
所以这里是“错误”的代码库:

public abstract class HtmlElement { /* ... */ }

public abstract class UIElement : HtmlElement { /* ... */ }

public abstract class ButtonBase : UIElement { /* ... */ }

public class LinkButton : ButtonBase { /* ... */ }

public class ActionButton : ButtonBase { /* ... */ }


public static class HtmlElementExtensions
{
  public static T Id<T>(this T item, string id) where T : HtmlElement
  {
    /* set the id */
    return item;
  }
}

public static class ButtonBaseExtensions
{
  public static T Id<T>(this T item, string id) where T : ButtonBase
  {
    /* set the id and do some button specific stuff*/
    return item;
  }
}

当我尝试在 LinkBut​​ton 上调用 Id 时,编译器会说存在模棱两可的调用:

LinkButton lb = new LinkButton().Id("asd");

我真的认为编译器在这种情况下会选择最接近的匹配,所以如果我有一个从 HtmlElement 继承的 Script 类而不是调用 HtmlExtensions Id 方法,并且对于 LinkBut​​ton(由于限制)将调用 ButtonBase 方法。 我有一个解决方案,但我不确定是否有更好的解决方案。
我从 ButtonBaseExtensions 中删除了 Id 方法,并按以下方式修改了 HtmlElementExtensions Id 方法:

public static T Id<T>(this T item, string id) where T : HtmlElement
{
  if (item is ButtonBase)
  {
    /* do some button specific stuff*/
  }
  /* set the id */
  return item;
}

这样,ButtonBase 的每个类后代都可以工作。 我不太喜欢我的解决方案,因为它将 HtmlElement 逻辑与 ButtonBase 逻辑混合在一起。 任何想法/建议以获得更好的解决方案? 我以为我将它们放在不同的名称空间中,但只是一秒钟。我应该同时使用两个命名空间,所以不要解决问题。

您认为值得在 msdn 论坛上提及编译器应该注意泛型扩展方法的限制吗?

与此同时,我进行了更多研究并在 msdn 论坛上创建了一个帖子:link
我尝试了一些非泛型扩展方法:

  public class BaseClass { /*...*/ }
  public class InheritedClass : BaseClass { /*...*/ }

  public static class BaseClassExtensions
  {
    public static void SomeMethod(this BaseClass item, string someParameter)
    {
      Console.WriteLine(string.Format("BaseClassExtensions.SomeMethod called wtih parameter: {0}", someParameter));
    }
  }

  public static class InheritedClassExtensions
  {
    public static void SomeMethod(this InheritedClass item, string someParameter)
    {
      Console.WriteLine(string.Format("InheritedClassExtensions.SomeMethod called wtih parameter: {0}", someParameter));
    }
  }

如果我实例化这些:

BaseClass bc = new BaseClass();
InheritedClass ic = new InheritedClass();
BaseClass ic_as_bc = new InheritedClass();

bc.SomeMethod("bc");
ic.SomeMethod("ic");
ic_as_bc.SomeMethod("ic_as_bc");

产生了这个输出:

BaseClassExtensions.SomeMethod called wtih parameter: bc
InheritedClassExtensions.SomeMethod called wtih parameter: ic
BaseClassExtensions.SomeMethod called wtih parameter: ic_as_bc

You can vote it for now

谢谢,
彼得

【问题讨论】:

  • 扩展方法不是虚拟方法的替代品,也不能成为虚拟方法。鉴于您自己声明了这些类,因此尚不清楚为什么需要一个。只需向其中一个基类添加一个虚拟方法,并在必要时在派生类中覆盖它。
  • 由于 fluent api 我必须使用扩展方法。泛型继承仅适用于 2 级继承。我用非泛型扩展方法编写了一个示例,其中编译器使用参数限制来确定要调用的正确方法。我只希望编译器也评估通用参数限制。我不能写“public static ButtonBase Id(this ButtonBase item, string id)”而不是“public static T Id(this T item, string id) where T : ButtonBase”,因为返回类型是 ButtonBase 和它打破了流利的API的方法链。

标签: .net c#-3.0 extension-methods


【解决方案1】:

您可以查看有关扩展方法的 MSDN 文档:Extension Methods (C# Programming Guide)。有趣的部分在在编译时绑定扩展方法

...它首先在类型的实例方法中查找匹配项。如果没有找到匹配项,它将搜索为该类型定义的任何扩展方法,并绑定到它找到的第一个扩展方法

所以这就是你看到这种行为的原因。我实际上可以购买它,想象一下有人可以用一种方法public static T Id&lt;T&gt;(this T item, string id) where T : object 覆盖您的应用程序的工作方式。如果你不会看到任何编译器错误,你会认为一切都是正确的,也许一切都会工作,除了一些极少数情况。会有多混乱?

还有一件关于你的方法的坏事。如果我将使用您的 API 并看到 Button 我有两种方法:一种在 HtmlElementExtensions 中,另一种在 ButtonBaseExtensions 中,什么会阻止我这样做 HtmlElementExtensions.Id(button, "id") 而不是 ButtonExtensions.Id(button, "id")

在你的情况下,我更喜欢组合方法:

public static T Id<T>(this T item, string id) where T : HtmlElement
{
  if (item is ButtonBase)
  {
      return (T)Id((ButtonBase)item);
  } 
  else if (item is HtmlElement)
  {
      return (T)Id((HtmlElement)item);
  }

  throw new NotSupportedException("Type " + item.GetType() + " is not supported by Id extension method");
}

private static ButtonBase Id(ButtonBase item, string id)
{
     return item;
}

private static HtmlElement Id(HtmlElement item, string id)
{
     return item;
}

【讨论】:

  • 在我的示例中,用户无法覆盖我的方法,因为在继承树中,ButtonBase 比对象更接近 LinkBut​​ton。你假设真正的恶意用户。我认为添加 throw new Exception(); 会简单得多。到代码出错。 :D 你的答案看起来像我的解决方案,只是你更优雅一点:)
  • @Péter 是的,对不起。我在考虑不同的方向。如果你有一个ButtonBase 并且有人会为LinkButton 编写扩展方法。是的,通过“组合方法”,我的意思是同时提供两种解决方案;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多