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
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.
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.
An extension can't be overridden because the way Swift implement extension is using static dispatch which means its resolved at compile time.
According to Microsoft, In fact, extension methods cannot access private variables in the type they are extending.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With