2011-02-02 71 views
3

我有一个抽象类水果。 然后我有一个派生类苹果C#泛型和特定扩展方法组合歧义

我有这两个扩展方法:

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>(); 
} 

现在,这里是通用的方法,我有一个存储库:

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); 
} 

这个问题我有,当我这样做:

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

它进入IQueryable<Fruit>扩展方法。

我想让它进入IQueryable<Apple>扩展方法。对于其他类型的水果,它应该进入IQueryable<TFruit>扩展方法。

我以为编译器会选择最具体的扩展方法。

任何想法?

编辑

感谢您的意见/答案。我现在看到为什么这不起作用。

那么有什么选择来解决这个问题?如果我创建了一个方法:

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派生类型。

任何人都可以提供一个替代方案,我试图做什么?

+1

泛型不是模板。 http://blogs.msdn.com/b/ericlippert/archive/2009/07/30/generics-are-not-templates.aspx – Ani 2011-02-02 04:50:29

+0

不确定这是否可能,因为`query.WithEagerLoading()`实际上只是简写对于`MyHelper.WithEagerLoading(query)`,它总是调用第一个泛型方法,而不是特定的方法。 – Vadim 2011-02-02 04:54:56

回答

0

扩展方法解析发生在编译时。查询变量的类型为IQueryable<TFruit>,因此编译器会选择与WithEagerLoading<Fruit>匹配的最具体的方法。它不能选择苹果,因为它只知道TFruit是某种Fruit。它选择一次,并永久。

你的建议是要求它根据运行时的类型动态地决定使用哪种扩展方法,或者编译根据TFruit的具体值不同地解析方法的单独版本IQueryable<TFruit>

编辑回答其他问题

好了,特别是外壳不超可怕的,因为你可以使用switch语句。但我同意,如果你有很多类型的话,这并不理想。在委托给子类方面,我会调整保罗的回答有点:

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

你的权利。看看我的编辑 - 你能想到另一种解决方案吗? – RPM1984 2011-02-02 06:09:48

1

最终你需要找到一种方法来引入一些多态 - 你想要加载苹果的特殊行为来扩展加载水果的基本行为。有时候,要做到这一点最简单的方法是让库类,如:

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覆盖FindByID。您的使用代码会有点不同,因为您必须确保您拥有的存储库实例适用于查找苹果。

如果您正在使用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容器...好..你应该:)

+0

是的,我使用的是一个IoC容器(就像所有好男孩一样),但我不想创建一个AppleRepository来满足一点行为。也许这是唯一的方法。 :( – RPM1984 2011-02-02 07:23:49

2

我有我想要的地方实际上是一个类似的问题改变一个方法的优先级,以便它首先解析一个'专用'版本。

您可以在不更改调用代码的情况下实现此目的,但该解决方案可能不受欢迎,因为它使用运行时反射和代码生成。无论如何,我只是想把它扔出去(我试着每天至少坚持一个答案!)。

请注意,此代码表示需要根据您的方案进行调整的抽象模式。

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's 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的工作正常 - 对不起,我没有让这个解决方案与您的场景更直接相关,它只是更容易地模拟测试解决方案这需要大部分通用地狱!