Péter
Péter

Reputation: 2181

How to "override" extension methods in .NET?

So I have an object hierarchy to produce ui controls in asp.net mvc and try to achive a fluent api. I make some dummy class to focus on the current problem.
So here is the "wrong" codebase:

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;
  }
}

When I try to call the Id on a LinkButton the compiler says there is ambiguous call:

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

I really thought that the compiler choose the closest match in this case so if I have a Script class that inherits from HtmlElement than HtmlExtensions Id method called, and for a LinkButton (because of the restrictions) the ButtonBase method will be called. I have a solution, but I'm not sure there is a better one.
I delete the Id method from ButtonBaseExtensions and modified the HtmlElementExtensions Id method as the following manner:

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;
}

This way every class descendant from the ButtonBase is working. I not really like my solution because it mix the HtmlElement logic with the ButtonBase logic. Any idea/suggestion for a better solution? I thought I put them in different namespace, but just for a second. I should have to use both namespace, so don't solve the problem.

Do you think it's worth to mention on the msdn forums as an idea that the compiler should watch the restrictions on generic extension methods?

In the meantime I make some more research and start a thread on msdn forums: link
I tried some non-generic extension methodds:

  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));
    }
  }

And if I instantiate these:

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");

Produced this output:

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

Thanks,
Péter

Upvotes: 8

Views: 3599

Answers (1)

outcoldman
outcoldman

Reputation: 11832

You can take a look on the MSDN documentation about extension methods: Extension Methods (C# Programming Guide). The interesting part is under Binding Extension Methods at Compile Time:

... it first looks for a match in the type's instance methods. If no match is found, it will search for any extension methods that are defined for the type, and bind to the first extension method that it finds.

So this is why you see this behavior. And I actually can buy it, just imagine that somebody can overwrite how your application works with just one method public static T Id<T>(this T item, string id) where T : object. And if you will not see any compiler errors, you will think that everything is correct, and maybe everything will work, except some rarely cases. How confusing it could be?

And one more bad thing about your approach. If I will consume your API and will see that for Button I have two methods: one in HtmlElementExtensions and one in ButtonBaseExtensions what will stop me of doing this HtmlElementExtensions.Id(button, "id") instead of ButtonExtensions.Id(button, "id")?

In your case I'd prefer combined approach:

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;
}

Upvotes: 3

Related Questions