C # General and special extension method Combination Ambiguity

I have an abstract class called Fruit. Then I got an Apple derived class .

I have two extension methods:

public static IQueryable<TFruit> WithEagerLoading<TFruit>(this IQueryable<TFruit> query) where TFruit : Fruit
{
   return query.EagerLoad(x => x.Distributors); // Fruit.Distributors
}

public static IQueryable<Apple> WithEagerLoading(this IQueryable<Apple> query)
{
   query = query.EagerLoad(x => x.AppleBrands); // Apple.AppleBrands

   // now bubble up to base extension method
   return query.WithEagerLoading<Apple>();
}

Now, here is the general method that I have in the repository:

public TFruit FindById<TFruit>(int fruitId) where TFruit : Fruit
{
   var query = _ctx.Fruits // IQueryable<Fruit>
                   .OfType<TFruit>(); // IQueryable<TFruit>

   query = query.WithEagerLoading();

   return query.SingleOrDefault(x => x.FruitId == fruitId);
}

I have a problem when I do this:

var apple = repository.FindById<Apple>(1);

It goes into the extension method IQueryable<Fruit>.

I want it to be part of the extension method IQueryable<Apple>. For other types of fruit, it should go into the extension method IQueryable<TFruit>.

I thought the compiler would choose the most specific extension method.

Any ideas?

EDIT

Thanks for the comments / response. Now I see why this does not work.

So what are the options to solve this problem? If I create a method:

public static IQueryable<Apple> WithAppleEagerLoading(this IQueryable<Apple> query)

? TFruit:

public TFruit FindById<TFruit>(int fruitId) where TFruit : Fruit
{
   var query = _ctx.Fruits // IQueryable<Fruit>
                   .OfType<TFruit>(); // IQueryable<TFruit>

   if (typeof(TFruit) == typeof(Apple))
       query = query.WithAppleEagerLoading();
   else 
       query = query.WithEagerLoading();

   return query.SingleOrDefault(x => x.FruitId == fruitId);
}

- , 20 .

- , ?

+3
3

. IQueryable<TFruit>, , WithEagerLoading<Fruit>. , , TFruit - Fruit. .

, , , , IQueryable<TFruit>, - TFruit.

, , switch. , , . , Paul:

abstract class FruitRepository : IRepository<T> where TFruit : Fruit
{
   public TFruit FindByID(int fruitID)
   {
       //query stuff here

       query = AddEagerLoading(query)
                  .WithEagerLoading();
    }

    //this could also be abstract to prevent you from forgetting
    public virtual IQueryable<TFruit> AddEagerLoading(IQueryable<TFruit> query)
    {
        return query;
    }
}

class AppleRepository : FruitRepository<Apple>
{
    public override AddEagerLoading(IQueryable<Apple> query)
    {
        return query.EagerLoad(x => x.AppleBrands);
    }
}

, .

0

, , "" .

, , , . ( SO!).

, , .

public class Base
{
  public string BaseString { get; set; }
}

public class Derived : Base
{
  public string DerivedString { get; set; }
}

public static class SO4870831Extensions
{
  private static Dictionary<Type, Action<Base>> _helpers = 
    new Dictionary<Type,Action<Base>>();

  public static void Extension<TBase>(this TBase instance) 
    where TBase :Base
  {
    //see if we have a helper for the absolute type of the instance
    var derivedhelper = ResolveHelper<TBase>(instance);

    if (derivedhelper != null)
      derivedhelper(instance);
    else
      ExtensionHelper(instance);
  }

  public static void ExtensionHelper(this Base instance)
  {
    Console.WriteLine("Base string: {0}", 
      instance.BaseString ?? "[null]");
  }

  /// <summary>
  /// By Default this method is resolved dynamically, but is also 
  /// available explicitly.
  /// </summary>
  /// <param name="instance"></param>
  public static void ExtensionHelper(this Derived instance)
  {
    Console.WriteLine("Derived string: {0}", 
      instance.DerivedString ?? "[null]");
    //call the 'base' version - need the cast to avoid Stack Overflow(!)
    ((Base)instance).ExtensionHelper();
  }

  private static Action<Base> ResolveHelper<TBase>(TBase instance) 
    where TBase : Base
  {
    Action<Base> toReturn = null;
    Type instanceType = instance.GetType();
    if (_helpers.TryGetValue(instance.GetType(), out toReturn))
      return toReturn;  //could be null - that fine

    //see if we can find a method in this class for that type
    //this could become more complicated, for example, reflecting
    //the type itself, or using attributes for richer metadata
    MethodInfo helperInfo = typeof(SO4870831Extensions).GetMethod(
      "BaseExtensionHelper", 
      BindingFlags.Public | BindingFlags.Static, 
      null, 
      new Type[] { instanceType }, 
      null);

    if (helperInfo != null)
    {
      ParameterExpression p1 = Expression.Parameter(typeof(Base), "p1");
      toReturn =
        Expression.Lambda<Action<Base>>(
        /* body */
          Expression.Call(
            helperInfo,
            Expression.Convert(p1, instanceType)),
        /* param */
          p1).Compile();
      _helpers.Add(instanceType, toReturn);
    }
    else
      //cache the null lookup so we don't expend energy doing it again
      _helpers.Add(instanceType, null);
    return toReturn;
  }
}
/// <summary>
/// Summary description for UnitTest1
/// </summary>
[TestClass]
public class UnitTest1
{
  [TestMethod]
  public void TestMethod1()
  {
    var a = new Base() { BaseString = "Base Only" };
    var b = new Derived() { DerivedString = "Derived", BaseString = "Base" };

    a.Extension();
    //Console output reads:
    //"Base String: Base Only"
    b.Extension();
    //Console output reads:
    //"Derived String: Derived"
    //"Base String: Base"
  }

, , , , , :)

, , . , ref/out, .

, , ( ).

, IQueryable - , , , !

+2

- , . - , :

class Repository<T> : IRepository<T>
{

   public virtual T FindById(int id)
   { ... }
}

class FruitRepository<T> : Repository<T> where T : Fruit
{
   public override T FindById(int id)
   {  ... }
}

class AppleRepository : FruitRepository<Apple>
{
   public override T FindById(int id)
   {  ... }
}

FindByID , . FruitRepository AppleRepository, . , , , .

IoC, , - IRepository<Apple>, AppleRepository. :

// ideally you would resolve this via constructor injection, but whatever.
var repository = container.Resolve<IRepository<Apple>>(); 
var apple = repository.FindByID(1);

IoC... .. :)

+1

Source: https://habr.com/ru/post/1789409/


All Articles