Shim和泛型方法

3

我想为一个通用方法创建一个Shim。但我在这种情况下遇到了一些问题。

以下是我的示例:

class BaseRepository <T> where T: Entity
{
    public T[] FindAll()
    {
        return Method<T>.FindAll()
    }
}

class ClassA : base<A>
{
}

class A : Entity
{
}

class ClassB : base<B>
{
}

class B : Entity
{
}

现在我想为ClassA和ClassB创建一个ShimMethod。

ShimBaseRepository<A>.AllInstances.FindAll = (repo) => MethodA();
ShimBaseRepository<B>.AllInstances.FindAll = (repo) => MethodB();

public A MethodA()
{
    //Make the Same as MethodB
}

public B MethodB()
{
    //Make the Same as MethodA
}

但是如果我有超过20个“Base”类怎么办?我不想为每个baseClass创建一个Delegate/方法。我尝试了以下方法:

List<Type> allEntityClasses = (from x in Assembly.GetAssembly(typeof(Entity)).GetTypes()
                                    where !x.IsAbstract && !x.IsInterface
                                    select x).ToList();

foreach(Type type in allEntityClasses=
{
    ShimBaseRepository<type????>.AllInstances.FindAll = (repo) => Method();
}

public Entity????? Method()
{
}

在我的单元测试中,我将使用以下方法:

ClassA.FindAll()
ClassB.FindAll()

而不是:

Base.FindAll()

编辑: 我使用 Microsoft Fakes,所以无法更改 ShimClass 中的任何内容。以下为从 Shim 生成的源代码。

  public class ShimBaseRepository<T> : ShimBase<BaseRepository<T>> where T : Entity
  {
    public static class AllInstances
    {
        public static FakesDelegates.Func<BaseRepository<T>, T[]> FindAll { [ShimMethod("FindAll", 20)] set; }
    }
  }

我的意图是,我不想为每个实体创建一个委托,我只想遍历所有的EntityClasses并动态地创建委托。但是我不知道如何将我的Type对象添加到中。

ShimBase<T>

你试图解决什么问题?你向我们展示了不起作用的解决方案,但没有展示实际存在的问题。此外,请确保你有可运行的示例代码。 - Luaan
1个回答

2
好的,让我们稍微讨论一下。首先,这里有一个使用虚方法的直接解决方案:
public class Base<T> where T : Entity
{
    public virtual T[] FindAll()
    {
        return null;
    }
}

然后在具体类中覆盖FindAll方法,或者如果可以的话,将Base声明为抽象类,并将InnerFindAll也声明为抽象方法。

但是,如果你需要在运行时指定委托(我看到你有一个特定的Helper用于此,但我无法理解为什么在Base中调用helper,然后在AllInstances中有一些未定义的Func),这种方法就不会起作用。你需要实现策略模式,并在Base中分配一些默认策略。然后,在具体类中,你将有三种方式来“解析”策略:

  1. 在具体类的构造函数中硬编码策略
  2. 通过DI容器将策略注入到具体类构造函数中
  3. 实现某种映射器,该映射器将为EntityType (T)返回适当的策略
另外,我认为您在设计方面有些困难。我不认为您需要将FindAll实现为注入到类型为Func<T>的静态属性的lambda表达式(是的,我认为您可以用static FindAll替换AllInstances.FindAll)。所以如果我是您,我会使用抽象方法...
编辑
现在我明白了您的问题,并且只能通过反射提供一个相当丑陋的解决方案...我强烈不建议您使用这个,因为它真的很复杂。
public class Program
{
    static void Main(string[] args)
    {
        List<Type> allEntityClasses = (from x in Assembly.GetAssembly(typeof(Entity))
                                           .GetTypes().Where(t=>typeof(Entity).IsAssignableFrom(t))
                                       where !x.IsAbstract && !x.IsInterface
                                       select x).ToList();
        foreach (var type in allEntityClasses)
        {
            var genericType = typeof(BaseGeneric<>).MakeGenericType(type);
            var helper = new DelegateHelper();
            var myLambda = helper.GetLambdaForType(type);
            var allInst = genericType.GetProperty("AllInstances").GetValue(null);
            if (allInst == null)
            {
                allInst = Activator.CreateInstance(genericType.GetProperty("AllInstances").PropertyType);
            }
            allInst.GetType().GetProperty("FindAll").SetValue(allInst,myLambda);
        }
    }


}

public static class BaseGeneric<T>
{
    public static AllInstances<T> AllInstances { get; set; }
}

public class AllInstances<T>
{
    public Func<T[]> FindAll { get; set; }
}

public class DelegateHelper
{
    public Delegate GetLambdaForType(Type type)
    {
        var funcType = typeof(Func<>).MakeGenericType(type.MakeArrayType());
        var methodInfo = typeof(DelegateHelper).GetMethods().FirstOrDefault(t => t.Name == "FunctionMethod")
                                               .MakeGenericMethod(type);
        var @delegate = methodInfo.CreateDelegate(funcType, this);
        return @delegate;
    }

    public T[] FunctionMethod<T>()
    {   
        return new T[10];
    }
}

public  class Entity
{
}

public class EntityFirst
{

}

public class EntitySecond
{

}

我使用Microsoft.Fakes进行测试,而不是编写这些类。我只想使用它。我添加了从Microsoft Fakes生成的源代码。 - wydy
这也是我第一次使用Microsoft Fakes,这就是我遇到问题的原因。 我有一个BaseRepository<T>,其中T:Entity,从接口获取一些数据。现在我编写了一些单元测试,我不想/不能通过此接口进行操作。所以我“伪造”了这个接口并覆盖了BaseRepository<T>方法。使用ShimBase<Entity>.AllInstances.FindAll = (repo) => Method();,我调用我的测试方法而不是接口方法。现在我必须为每个EntityClass创建一个委托,否则程序会尝试通过接口进行操作。但是大约有100个EntityClasses... - wydy
@wydy 我更新了我的答案,请问这是否有所帮助。它使用了很多反射,因此我认为它非常丑陋且难以维护。 - Alex Voskresenskiy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接