Christophe Gigax
Christophe Gigax

Reputation: 3470

Extension methods with interface

Assume we have this model :

public abstract class AbstractTableReferentielEntity {}
public class EstimationTauxReussite : AbstractTableReferentielEntity { }

I created a extension method for all classes that inherit from AbstractTableReferentielEntity.

public static EntityItemViewModel ToEntityItem<T>(this T entity)
    where T : AbstractTableReferentielEntity {}

But for one specific type of AbstractTableReferentielEntity (like EstimationTauxReussite), I would like to perform a specific action, so I created a second extension method.

 public static EntityItemViewModel ToEntityItem(this EstimationTauxReussite entity) {}

All extensions methods are declared in the same namespace.

After that, I retrieve some data from a DB with Entity Framework :

protected List<EntityItemViewModel> GetAllActifEntityItem<T>()
    where T : AbstractTableReferentielEntity
{
    return Context
        .Set<T>()
        .Where(item => item.IsActif)
        .Select(item => item.ToEntityItem())
        .ToList();
}

It compiles.

When T at runtime is a EstimationTauxReussite type, it goes into the wrong method ToEntityItem when I call Select(item => item.ToEntityItem()). It doesn't go into the most specific extension method. Any ideas ?

Upvotes: 6

Views: 464

Answers (3)

Thomas Levesque
Thomas Levesque

Reputation: 292355

That's because extension methods are just syntactic sugar for static methods. The method to call is resolved at compile time based on the compile-time type of the argument, there is no virtual dispatch involved.

In your GetAllActifEntityItem method, the compiler only knows that T is a AbstractTableReferentielEntity, so it resolves the ToEntityItem method based on that. The fact that it will actually be called with EstimationTauxReussite for T is irrelevant.

A possible workaround would be to make ToEntityItem a virtual member method of AbstractTableReferentielEntity, and override it in EstimationTauxReussite. This way, virtual dispatch will occur as expected and the correct method will be called.

Upvotes: 1

David Arno
David Arno

Reputation: 43254

The reason is that extension methods are "syntactic sugar", ie they are a compiler trick. Your line:

.Select(item => item.ToEntityItem())

is effectively converted by the compiler to:

.Select(item => StaticClassWithExtensionMethod.ToEntityItem(item))

and then turned into IL. This means that the type of item has to be determined at compile-time, not runtime. So the AbstractTableReferentielEntity version of the extension method is used as that's the one tht matches the type at compile time.

Upvotes: 3

Disappointed
Disappointed

Reputation: 1120

If i have access to sources of AbstractTableReferentielEntity and EstimationTauxReussite classes i would remake them in following way

  1. Add virtual ToEntityItem method to AbstractTableReferentielEntity class
  2. Override it in EstimationTauxReussite class
  3. Delete both extension methods

Now Select(item => item.ToEntityItem()) should pick method depends on input object

Upvotes: 1

Related Questions