2011-03-24 33 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; 
}