C#通用和专用扩展方法组合歧义
我有一个名为水果的抽象类。 然后我有一个名为Apple的派生类 我有以下两种扩展方法:C#通用和专用扩展方法组合歧义,c#,generics,extension-methods,type-parameter,C#,Generics,Extension Methods,Type Parameter,我有一个名为水果的抽象类。 然后我有一个名为Apple的派生类 我有以下两种扩展方法: public static IQueryable<TFruit> WithEagerLoading<TFruit>(this IQueryable<TFruit> query) where TFruit : Fruit { return query.EagerLoad(x => x.Distributors); // Fruit.Distributors }
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>();
}
这可不好——考虑到我有大约20个派生类型
有人能为我的尝试提供一个替代方案吗?扩展方法解析在编译时进行。查询变量的类型为
IQueryable
,因此编译器会选择与匹配的最具体的方法。它不能摘苹果,因为它只知道水果是某种水果。它只会选一次,而且是永久性的
您的建议要求它根据运行时的类型动态决定使用哪个扩展方法,或者编译独立版本的IQueryable
,根据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;
}
}
抽象类FruitRepository:IRepository,其中TFruit:Fruit
{
公共TFruit FindByID(int FROUTID)
{
//在这里查询资料
查询=加载(查询)
.使用加载();
}
//这也可以是抽象的,以防止您忘记
公共虚拟IQueryable加载(IQueryable查询)
{
返回查询;
}
}
然后
class AppleRepository : FruitRepository<Apple>
{
public override AddEagerLoading(IQueryable<Apple> query)
{
return query.EagerLoad(x => x.AppleBrands);
}
}
class AppleRepository:存储库
{
公共覆盖加载(IQueryable查询)
{
返回query.eangerload(x=>x.AppleBrands);
}
}
这样,每个子类的代码就最少了。最终,你需要找到一种引入多态性的方法——你想要加载苹果的特殊行为,它扩展了加载水果的基本行为。有时最简单的方法是创建存储库类,例如:
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)
{ ... }
}
类存储库:IRepository
{
公共虚拟T FindById(int-id)
{ ... }
}
类FruitRepository:Repository,其中T:Fruit
{
公共覆盖T FindById(int id)
{ ... }
}
类AppleRepository:FructureRepository
{
公共覆盖T FindById(int id)
{ ... }
}
现在FindByID不需要在方法级别使用泛型参数,它只在类级别使用泛型参数。然后,您可以根据需要找到相应的FruitRepository和AppleRepository override。您的使用代码可能有点不同,因为您必须确保您拥有的存储库实例适合查找苹果
如果您使用的是IoC容器,您可能会注册,当某个对象请求IRepository
时,它会返回一个AppleRepository实例。那么你可以这样使用它:
// ideally you would resolve this via constructor injection, but whatever.
var repository = container.Resolve<IRepository<Apple>>();
var apple = repository.FindByID(1);
//理想情况下,您可以通过构造函数注入来解决这个问题,但不管怎样。
var repository=container.Resolve();
var apple=repository.FindByID(1);
如果您没有使用IoC容器。。。好。。您应该是:)我遇到了一个类似的问题,我想有效地更改方法的优先级,以便它首先解决“专用”版本
您可以在不更改调用代码的情况下实现这一点,但该解决方案可能并不流行,因为它使用运行时反射和代码生成。不管怎样,我还是要把它扔出去(我试着每天至少给出一个答案!)
请注意,此代码表示需要根据您的场景进行调整的抽象模式
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"
}
公共类基
{
公共字符串基串{get;set;}
}
派生的公共类:基
{
公共字符串DerivedString{get;set;}
}
公共静态类SO4870831扩展
{
专用静态词典_helpers=
新字典();
公共静态无效扩展(此TBase实例)
其中TBase:Base
{
//查看是否有实例绝对类型的帮助器
var derivedheloper=resolveheloper(实例);
如果(derivedhelper!=null)
derivedhelper(实例);
其他的
ExtensionHelper(实例);
}
公共静态void ExtensionHelper(此基本实例)
{
WriteLine(“基字符串:{0}”,
instance.BaseString??“[null]”;
}
///
///默认情况下,此方法是动态解析的,但也是
///明确提供。
///
///
公共静态void ExtensionHelper(此派生实例)
{
WriteLine(“派生字符串:{0}”,
instance.DerivedString??“[null]”;
//调用“基本”版本-需要强制转换以避免堆栈溢出(!)
((基本)实例);
}
私有静态操作ResolveHelper(TBase实例)
其中TBase:Base
{
返回操作=null;
类型instanceType=instance.GetType();
if(_helpers.TryGetValue(instance.GetType(),out-toReturn))
return toReturn;//可以为null-没关系
//看看我们是否能在此类中找到该类型的方法
//这可能会变得更复杂,例如,反射
//类型本身,或使用更丰富元数据的属性
MethodInfo helperInfo=typeof(SO4870831Extensions).GetMethod(
“BaseExtensionHelper”,
BindingFlags.Public | BindingFlags.Static,
无效的
新类型[]{instanceType},
无效);
if(helperInfo!=null)
{
ParameterExpression p1=表达式参数(typeof(Base),“p1”);
返回=
Lambda(
/*身体*/
表情,打电话(
助手信息,
Expression.Convert(p1,instanceType)),
/*param*/
p1).编译();
_添加(instanceType,toReturn);
}
其他的
//缓存空查找,这样我们就不会再次花费精力了
_助手
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)
{ ... }
}
// ideally you would resolve this via constructor injection, but whatever.
var repository = container.Resolve<IRepository<Apple>>();
var apple = repository.FindByID(1);
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"
}