【问题标题】:Shim and Generic Methods垫片和通用方法
【发布时间】:2015-05-19 13:47:57
【问题描述】:

我想为通用方法创建一个 Shim。但在那种情况下,我对 Generic 有点麻烦。

这是我的例子:

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 个“基础”类怎么办?我不想为每个基类创建一个委托/方法。我尝试过这样的事情:

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>

【问题讨论】:

  • 您要解决的问题是什么?您向我们展示了不起作用的解决方案,而不是您实际遇到的问题。另外,请确保您确实有可以运行的示例代码。

标签: c# generics shim


【解决方案1】:

好的,让我们稍微讨论一下。 首先,这是一个使用虚拟方法的直接解决方案:

public class Base<T> where T : Entity
{
    public virtual T[] FindAll()
    {
        return null;
    }
}

然后在具体类中覆盖FindAll 或者,如果可以的话,将Base 抽象化,并将InnerFindAll 抽象化。

但是,如果您需要在运行时指定委托(我可以看到您有一个特定的 Helper,但我不明白,为什么您在 Base 中调用 helper,然后您有一些未定义的问题 AllInstances Func) 这种方法无济于事。您需要使用 Base 中分配的一些默认策略来实现 Strategy pattern。然后,您将有 3 种方法来“解决”具体类中的策略:

  1. 在具体类的构造函数中硬编码策略
  2. 通过 DI 容器向具体类构造函数注入策略
  3. 实现某种映射器,它将为您返回适当的 EntityType 策略 (T)

另外,我认为您在设计方面遇到了一些问题。我看不出有任何理由需要将 FindAll 实现为注入到 Func&lt;T&gt; 类型的静态属性的 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 生成的源代码。
  • 这也是我第一次使用 Microsoft Fakes,这也是我遇到问题的原因。我有我的 BaseRepository where T :从接口获取一些数据的实体。现在我写了一些单元测试,我不想/不能通过这个接口。所以我“伪造”了这个接口并覆盖了 BaseRepository 方法。使用 ShimBase.AllInstances.FindAll = (repo) => Method();我调用我的测试方法而不是接口方法。现在我必须为每个 EntityClass 创建一个委托,否则程序会尝试通过接口。但是大约有 100 个 EntityClasses...
  • @wydy 我更新了我的答案,请让我知道这是否有帮助。它使用了很多反射,所以我觉得它真的很丑而且不可维护
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-13
  • 2013-04-15
  • 2014-10-11
  • 1970-01-01
  • 1970-01-01
  • 2019-09-19
  • 2012-11-05
相关资源
最近更新 更多