У меня была похожая проблема, когда я хотел эффективно изменить приоритет метода, чтобы он сначала разрешил «специализированную» версию.
Вы можете достичь этого, не изменяя свой вызывающий код, но решение может быть непопулярным, поскольку оно использует отражение во время выполнения и генерацию кода. В любом случае, я просто собираюсь его там (я пытаюсь выложить как минимум один ответ в день на ТАК!).
Обратите внимание, что этот код представляет собой абстрактный шаблон, который необходимо адаптировать к вашему сценарию.
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 - извините, я не сделал это решение более прямо относящимся к вашему сценарию, просто проще смоделировать тестовое решение, которое выводит большую часть общего ада!