2011-03-24 62 views
1

首先,我想指出,我已经有一个工作解决方案,但我正在尝试查看是否有方法使代码更清晰并且不那么繁琐。推断基于另一个指定泛型类型的泛型类型并使用它

这是我的情况。我实际上已经简化了情况,并创建了一个假的例子来说明清楚。我只是要展示一个具体的例子,展示我已经完成了什么,它的工作原理。

假设我们有这些类:

public abstract class Shape{ //...elided... } 
public class Square : Shape { //...elided... } 
public class Circle : Shape { //...elided... } 

并假设有某种类的,做一些与他们这样的:

public class ShapeThingy 
{ 
    public static void MakeSquaresDance(List<Squares> squares){ //...elided... } 
    public static void RollCircles(List<Circles> circles){ //...elided... } 
} 

现在假设我想测试ShapeThingy类。假设对于某些测试,我想用MockSquares和MockCircles代替正方形和圆形的列表。另外,假设设置MockCircles和MockSquares非常相似,这样我想有一种方法来创建模拟形状列表,并且告诉这种方法我需要的形状类型。下面是我如何实现它:

public class Tests 
{ 
     [Test] 
     public void TestDancingSquares() 
     { 
      List<Squares> mockSquares = GetMockShapes<Square, MockSquare>(); 
      ShapeThingy.MakeSquaresDance(mockSquares); 

      Assert.Something(); 
     } 

     [Test] 
     public void TestRollingCircles() 
     { 
      List<Circles> mockCircles = GetMockShapes<Circle, MockCircle>(); 
      ShapeThingy.RollCircles(mockCircles); 

      Assert.Something(); 
     } 


     private List<TBase> GetMockShapes<TBase, TMock>() 
     where TBase : Shape 
     where TMock : TBase, new() 
     { 
     List<TBase> mockShapes = new List<TBase>(); 

     for (int i = 0; i < 5; i++) 
     { 
      mockShapes.Add(MockShapeFactory.CreateMockShape<TMock>()); 
     } 
     } 

} 

public class MockSquare : Square { //...elided... } 
public class MockCircle : Circle { //...elided... } 

public class MockShapeFactory 
{ 
    public static T CreateMockShape<T>() 
     where T : Shape, new() 
    { 
     T mockShape = new T(); 
     //do some kind of set up 
     return mockShape; 
    } 
} 

现在这工作正常。我遇到的问题是,您已经指定了GetMockShapes()列表的所需输出类型以及您实际希望列表包含的模拟类型。实际上,我已经知道如果我要求GetMockShapes()获得列表<Square>,那么它实际上应该被MockSquare填充。必须一遍又一遍地指定这两件事情是很麻烦的。

我想要做的是这样的:

 private List<TBase> GetMockShapes<TBase>() 
     where TBase : Shape 
     { 
     List<TBase> mockShapes = new List<TBase>(); 

     Type mockType = getAppropriateMockType<TBase>(); 

     for (int i = 0; i < 5; i++) 
     { 
      //compiler error: typeof(mockType) doesn't work here 
      mockShapes.Add(MockShapeFactory.CreateMockShape<typeof(mockType)>()); 
     } 
     } 

     private Type getAppropriateMockType<TBase>() 
     { 
     if(typeof(TBase).Equals(typeof(Square))) 
     { 
      return typeof(MockSquare); 
     } 

     if(typeof(TBase).Equals(typeof(Circle))) 
     { 
      return typeof(MockCircle); 
     } 

     //else 
     throw new ArgumentException(typeof(TBase).ToString() + " cannot be converted to a mock shape type."); 
     } 

     //add then a test would look like this 
     //(one less word, one less chance to screw up) 
     [Test] 
     public void TestDancingSquares() 
     { 
      List<Squares> mockSquares = GetMockShapes<Square>(); 
      ShapeThingy.MakeSquaresDance(mockSquares); 

      Assert.Something(); 
     } 

的问题是该版本将无法编译,我想不出办法解决它。也许我想做的事是不可能的。

在这一点上,你可能会想

现在,“如果他只是使用了IEnumerable <牛逼>而不是List <牛逼>,那么他就可以利用在C#4.0协方差的,他不会做任何的这个废话“,这是真的,但在我们真实的代码中,我们并没有使用List <T>,而是使用自定义具体类型,Something <T>(并且它不是IEnumerable样式集合),而且我不' t有能力改变Something <T>的使用,并且现在引入一个协变接口ISomething < out T >。不管怎么说,我想要做的所有事情,我想,只要我打电话给GetMockShapes(),就不用再输入一个额外的单词,所以它不是真的那么重要,我也不知道,也许,也许这两种类型都是明确的,以便看清楚是很好的。我只是想,如果我能找到某种方式来做到这一点,那将是一件很酷的事情,我也会学到一些新的东西。我主要想知道这是否能够满足我的好奇心。在代码质量方面,我认为这并不重要。

+0

好吧,在你的例子中它不会编译,因为'getAppropriateMockType'没有被指定为通用的。将它指定为具有与第一个方法相同的约束的泛型,它会起作用吗? – 2011-03-24 08:30:51

+0

啊,谢谢。这解决了两个编译器错误之一,但还有另一个。我纠正了这些代码,并且还添加了一条评论来显示仍然导致问题的部分。 – olanmills 2011-03-24 09:09:35

+0

查看回答发贴数。 – 2011-03-24 10:00:57

回答

0

好了现在的问题是你不能用Type实例调用泛型,你需要一个编译时类型的句柄。

要解决这个问题,您可以:

  • 修改MockShapeFactory.CreateMockShape<T>方法采取Type实例,而不是把它写成通用的 - 但实例的实际创建可能会更难呢。

  • 使用反射动态绑定到CreateMockShape方法的“正确”版本(基于从getAppropriateMockType返回的类型)。

对于第二个 - 这个测试代码可能证明是有益的:

#region some stubs (replaced with your types) 

public class Shape { } 
public class MockSquare : Shape { } 
public class MockCircle : Shape { } 

public class MockShapeFactory 
{ 
    //I've added a constraint so I can new the instance 
    public static T CreateMockShape<T>() 
    where T : Shape, new() 
    { 
    Console.WriteLine("Creating instance of {0}", typeof(T).FullName); 
    return new T(); 
    } 
} 

#endregion 

//you can cache the reflected generic method 
System.Reflection.MethodInfo CreateMethodBase = 
    typeof(MockShapeFactory).GetMethod(
    "CreateMockShape", 
    System.Reflection.BindingFlags.Public 
    | System.Reflection.BindingFlags.Static 
); 

[TestMethod] 
public void TestDynamicGenericBind() 
{ 
    //the DynamicBindAndInvoke method becomes your replacement for the 
    //MockShapeFactory.CreateMockShape<typeof(mockType)>() call 
    //And you would pass the 'mockType' parameter that you get from 
    //getAppropriateMockType<TBase>(); 
    Assert.IsInstanceOfType 
    (DynamicBindAndInvoke(typeof(MockCircle)), typeof(MockCircle)); 

    Assert.IsInstanceOfType 
    (DynamicBindAndInvoke(typeof(MockSquare)), typeof(MockSquare)); 
} 
//can change the base type here according to your generic 
//but you will need to do a cast e.g. < 
public Shape DynamicBindAndInvoke(Type runtimeType) 
{ 
    //make a version of the generic, strongly typed for runtimeType 
    var toInvoke = CreateMethodBase.MakeGenericMethod(runtimeType); 
    //should actually throw an exception here. 
    return (Shape)toInvoke.Invoke(null, null); 
} 

它看起来比它差 - 我们的目标是,以取代一个接受一个Type调用工厂的泛型方法实例 - 这是DynamicBindAndInvoke(Type)在这个例子中所做的。这个测试看起来可能毫无意义 - 但这只是因为我在编译时已知的类型 - 在您的情况下,传递的类型将是从您的getAppropriateMockType方法中检索的类型。

请注意,我假设你的工厂方法在这里是一个静态的MockShapeFactory。如果不是,则反射和调用代码将不得不更改为搜索实例方法并将工厂实例作为第一个参数传递给Invoke

这种模式可以扩展为编译委托,从而加快了速度,但对于那种优化可能毫无意义的测试环境。

+0

感谢您的解决方案。 – olanmills 2011-03-25 17:15:51

0

我不确定这是一种很好的做事方式,但我得到了GetMockShapes方法以您寻找的方式工作。这个想法是从MockShapeFactory开始,得到其CreateMockShape方法,将其转换为适当的通用版本并调用它来创建正确类型的对象。

虽然得到了object,而mockShapesAdd方法只接受正确类型的Shape。我无法弄清楚如何动态地将新的mockShape转换为适当的类型。我想,这样可以避免无论如何都要通过反思调用建筑商。

我绕过了类型检查系统(就像我说的,“不知道这是一个很好的方法来做事情)”。我从mockShapes列表开始,得到它的运行时类型,得到它的Add方法,并用新创建的对象调用它。编译器需要该方法的对象并允许这个;反射会在运行时强制正确输入。如果GetAppropriateMockType返回不合适的类型,可能会发生不好的事情。

using System.Collections.Generic; 
using System.Reflection; 
using System; 

private List<TBase> GetMockShapes<TBase>() 
    where TBase : Shape 
{ 
    Type TMock = getAppropriateMockType<TBase>(); 

    // Sanity check -- if this fails, bad things might happen. 
    Assert(typeof(TBase).IsAssignableFrom(TMock)); 

    List<TBase> mockShapes = new List<TBase>(); 

    // Find MockShapeFactory.CreateMockShape() method 
    MethodInfo shapeCreator = typeof(MockShapeFactory).GetMethod("CreateMockShape"); 

    // Convert to CreateMockShape<TMock>() method 
    shapeCreator = shapeCreator.MakeGenericMethod(new Type[] { TMock }); 

    for (int i = 0; i < 5; i++) 
    { 
     // Invoke the method to get a generic object 
     // The object to invoke on is null because the method is static 
     // The parameter array is null because the method expects no parameters 
     object mockShape = shapeCreator.Invoke(null, null); 

     mockShapes.GetType()     // Get the type of mockShapes 
      .GetMethod("Add")    // Get its Add method 
      .Invoke(      // Invoke the method 
       mockShapes,     // on mockShapes list 
       new object[] { mockShape }); // with mockShape as argument. 
    } 

    return mockShapes; 
} 

一个更好的(但针对具体情况)的方式

后一些更多的思考,我意识到这里有一种不成文的假设,你可以滥用。您正在尝试制作一个List<TBase>并填写TMockTMock的整点是模仿TBase,所以TMockTBase。实际上,List甚至使用TBase作为其类型参数。

这很重要,因为这意味着您不必将通用对象投射到TMock,您可以将其投射到TBase。由于TBase在编译时已知,因此可以使用简单的静态转换,而不是绕过键入系统将通用对象传递给类型化方法。我认为如果你可以使用它,这种方式会好很多。

using System.Collections.Generic; 
using System.Reflection; 
using System; 

private List<TBase> GetMockShapes<TBase>() 
    where TBase : Shape 
{ 
    Type TMock = getAppropriateMockType<TBase>(); 

    // Sanity check -- if this fails, bad things might happen. 
    Assert(typeof(TBase).IsAssignableFrom(TMock)); 

    List<TBase> mockShapes = new List<TBase>(); 

    // Find MockShapeFactory.CreateMockShape() method 
    MethodInfo shapeCreator = typeof(MockShapeFactory).GetMethod("CreateMockShape"); 

    // Convert to CreateMockShape<mockType>() method 
    shapeCreator = shapeCreator.MakeGenericMethod(new Type[] { TMock }); 

    for (int i = 0; i < 5; i++) 
    { 
     // Invoke the method to get a generic object 
     // The object to invoke on is null because the method is static 
     // The parameter array is null because the method expects no parameters 
     object mockShape = shapeCreator.Invoke(null, null); 

     // 
     // Changes start here 
     // 

     // Static cast the mock shape to the type it's impersonating 
     TBase mockBase = (TBase)mockShape; 

     // Now this works because typeof(mockBase) is known at compile time. 
     mockShapes.Add(mockBase); 
    } 

    return mockShapes; 
}