loading...

Generic extension methods ambiguity in C#

lauromoura profile image Lauro Moura ・2 min read

As part of our work in the C# bindings for EFL, recently I stumbled upon a kinda non-obvious issue using C#'s extension methods.

While implementing support for the MVVM factories, extension methods on the factories were generated so you could bind properties from the model to properties of the objects that would be created. For example

var factory = new Efl.ItemFactory<ListItem>();
factory.Text().bind("modelProperty");

The factory will create instances of ListItem and upon instantiating them, will bind the modelProperty of the associated model element to the Text property of the newly created ListItem so whenever the model changes, the created widget shows the updated value.

To make these properties available, the following extension methods were generated for each property in each "wrappable" class (i.e. class that could go inside a factory):

/// Extensions for ListItem
static Bindable<string> Text<T>(this ItemFactory<T> fac) where T : ListItem
{
  ...
}
/// Extensions for GridItem
static Bindable<string> Text<T>(this ItemFactory<T> fac) where T : GridItem
{
  ...
}

So far, so good. But the code above has an important limitation. The where constraint is not part of the signature. And if we tried to call one of these extension methods that have the same name but two different Ts, ambiguity arises and the compilation will fail with CS0121 The call is ambiguous between the following methods or properties:....

The solution we used (thank god Stack Overflow) was to add an extra default parameter dependent on the T and the type of the constraint. This would allow selecting an unique method based on the generic type parameter.

Using said method, we could do something like:

/// Magic tag to select the unique method
class MagicTag<TBase, TInherited> : where TInherited : TBase
{ }

/// Extensions for ListItem
static Bindable<string> Text<T>(this ItemFactory<T> fac, MagicTag<ListItem, T> x=null) where T : ListItem
{
  ...
}
/// Extensions for GridItem
static Bindable<string> Text<T>(this ItemFactory<T> fac, MagicTag<GridItem, T> x=null) where T : GridItem
{
  ...
}

This successfully gives enough info to the compiler select the single correct method without ambiguity in most cases. The exception is where we would have two methods with the same name and TInherited is actually a derived class from TBase. But in our case the code generator avoids generating repeated methods for inherited classes, bypassing this issue.

o/

Discussion

markdown guide