Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 783
Péter Avatar asked Apr 22 '13 06:04

Péter


People also ask

Can I override an extension method?

Extension methods cannot be overridden the way classes and instance methods are. They are overridden by a slight trick in how the compiler selects which extension method to use by using "closeness" of the method to the caller via namespaces.

Can you override methods in C#?

override (C# reference)An override method provides a new implementation of the method inherited from a base class. The method that is overridden by an override declaration is known as the overridden base method. An override method must have the same signature as the overridden base method.

Can we override extension method Swift?

An extension can't be overridden because the way Swift implement extension is using static dispatch which means its resolved at compile time.

Can extension methods access private methods?

According to Microsoft, In fact, extension methods cannot access private variables in the type they are extending.


1 Answers

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;
}
like image 88
outcoldman Avatar answered Sep 28 '22 10:09

outcoldman