如何在.NET中“覆盖”扩展方法?

7

我有一个对象层次结构,用于在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;
  }
}

当我尝试调用LinkButton上的Id时,编译器会提示存在模糊的调用:

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

我本以为编译器会在这种情况下选择最接近的匹配项,因此如果我有一个从HtmlElement继承的Script类,那么将调用HtmlExtensions Id方法,对于LinkButton(由于限制),将调用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论坛上发起了一个帖子:链接
我尝试了一些非泛型的扩展方法:

  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

现在你可以为此投票

谢谢,
彼得


1
扩展方法并不是虚方法的替代品,也不能将其设置为虚方法。鉴于这些类是由你自己声明的,因此很难理解为什么你需要一个扩展方法。只需在其中一个基类中添加虚方法,并在派生类中必要时进行重写即可。 - Hans Passant
由于流畅的API,我必须使用扩展方法。泛型继承只适用于2级继承。 我写了一个非泛型扩展方法的示例,其中编译器使用参数限制来确定要调用的正确方法。我希望编译器也能评估泛型参数限制。我不能写“public static ButtonBase Id(this ButtonBase item, string id)”而不是“public static T Id<T>(this T item, string id) where T : ButtonBase”,因为返回类型将是ButtonBase,并且它会破坏流畅API的方法链。 - Péter
1个回答

3
你可以查看MSDN文档关于扩展方法的部分:Extension Methods (C# Programming Guide),有趣的部分在Binding Extension Methods at Compile Time下:

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

这就是你看到这种行为的原因。我认为这种情况很容易理解,想象一下有人只需一个方法public static T Id<T>(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比对象更接近LinkButton。而且你假设了一个非常恶意的用户。我认为在代码中添加throw new Exception();更简单,以便产生错误。 :D 你的答案看起来很像我的解决方案,只是你的更加优雅 :) - Péter
@Péter,对不起。我想错了方向。如果你有一个ButtonBase,并且有人为LinkButton编写扩展方法。是的,“组合方法”指的是同时使用你的两种解决方案;) - outcoldman

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