根据另一个指定的泛型类型推断泛型类型并使用它
首先,我想指出,我已经有了一个可行的解决方案,但我正在尝试看看是否有一种方法可以使代码更干净、不那么麻烦。
这是我的情况。我实际上已经简化了情况并创建了一个假例子来使说明更清楚。我只是想举一个具体的例子来展示我已经做了什么,而且它是有效的。
假设我们有这些类:
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 替换为列表中的 Squares 和 Circles。另外,假设设置 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() 请求 List
我想做的是这样的:
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
不管怎样,我想,我想做的就是试图让自己不必在每次调用 GetMockShapes() 时额外输入一个单词,所以这并不是什么大不了的事,而且我不知道,也许两者都很好类型已指定,以便一目了然。我只是觉得如果我能找到一些方法来做到这一点那就太酷了,而且我也能学到一些新东西。我主要想知道是否可以这样做来满足我的好奇心。我认为就代码质量而言这并不是那么重要。
First of all, I want to point out, that I already have a working solution, but I am trying to see if there is a way to make the code cleaner and less cumbersome.
Here is my situation. I have actually simplified the situation and created a fake example to make the illustration clear. I am just going to lay out a concrete example showing what I have already done, and it works.
Suppose we have these classes:
public abstract class Shape{ //...elided... }
public class Square : Shape { //...elided... }
public class Circle : Shape { //...elided... }
And suppose there's some kind of class that does something with them like this:
public class ShapeThingy
{
public static void MakeSquaresDance(List<Squares> squares){ //...elided... }
public static void RollCircles(List<Circles> circles){ //...elided... }
}
Now suppose I want to test the ShapeThingy class. Suppose that for some of the tests, I want to substitute MockSquares and MockCircles into the lists in place of Squares and Circles. Also, suppose that setting up the MockCircles and the MockSquares is very similar, such that I want to have one method to create the lists of mock shapes, and I tell this method the type of shape that I need. Here is how I have implemented it:
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;
}
}
Now this works fine. The problem I have with it is that you have specify to GetMockShapes() both the desired output type of the list, and the mock type that you actually want the list to contain. When in reality, I already know that if I ask GetMockShapes() for List<Square>, then it should actually be filled with MockSquare. It's kind of cumbersome to have to specify both things over and over.
What I want to do is something like this:
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();
}
The problem is that version won't compile, and I can't figure out a way around it. Maybe what I want to do is not possible.
Now at this point you may be thinking, "If he just uses IEnumerable<T> instead of List<T>, then he can take advantage of covariance in C# 4.0 and he won't have to do any of this crap," which is true, but but in our real code, we are not using List<T>, but rather a custom concrete type, Something<T> (and it is not an IEnumerable-style collection), and I don't have the ability to change the usage of Something<T> and introduce a covariant interface ISomething<out T> right now.
Anyways, all I am trying to do, I guess, is trying to save myself from having to type one extra word whenever I call GetMockShapes(), so it's not really that big of a deal, and I dunno, maybe it's good that both types are specified so that it's plain to see. I just thought it would be cool if I could figure out some way to do this, and I would learn something new as well. I mostly want to know if this can be done to satisfy my curiosity. I don't think it's really that important in terms of code quality.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好吧,现在的问题是您无法使用
Type
实例调用泛型,您需要一个编译时类型句柄。要解决这个问题,您可以:
修改
MockShapeFactory.CreateMockShape
方法以获取Type
实例,而不是将其写为通用实例 - 但实际创建那么实例可能会更难。使用反射动态绑定到 CreateMockShape 方法的“正确”版本(基于从
getAppropriateMockType
返回的类型)。对于第二个 - 这个测试代码可能会有所帮助:
它看起来比实际情况更糟糕 - 目标是用接受
Type
实例的方法替换对工厂泛型方法的调用 - 这就是DynamicBindAndInvoke(Type)
在此示例中执行此操作。在此测试中,它可能看起来毫无意义 - 但这只是因为我输入的是编译时已知的类型 - 在您的情况下,传递的类型将是从您的 getAppropriateMockType 方法中检索到的类型。请注意,我假设您的工厂方法是
MockShapeFactory
上的静态方法。如果不是,则必须更改反射和调用代码以搜索实例方法并将工厂实例作为第一个参数传递给Invoke
。这种模式可以扩展到编译委托,从而加快速度,但对于测试环境来说,这种优化可能毫无意义。
Okay the problem now is that you can't invoke a generic with a
Type
instance, you need a compile-time type handle for that.To get around this you can:
Modify the
MockShapeFactory.CreateMockShape<T>
method to take aType
instance rather than write it as generic - but the actual creation of the instance would probably be harder then.Dynamically bind to the 'correct' version of the CreateMockShape method (based on the type returned from
getAppropriateMockType
) using reflection.For the second - this test code might prove helpful:
It looks worse than it is - the goal is to replace the call to the factory's generic method with one that accepts a
Type
instance - which is whatDynamicBindAndInvoke(Type)
does in this example. It might look pointless in this test - but that's only because I'm feeding in types known at compile time - in your case the type being passed would be the one retrieved from yourgetAppropriateMockType
method.Note that I've assumed that your factory method is a static on
MockShapeFactory
here. If it's not, then the reflection and invoke code would have to change to search for an instance method and to pass the instance of the factory as the first parameter toInvoke
.This pattern can be extended to compile delegates, thus speeding it all up, but for a test environment that kind of optimisiation is probably pointless.
我不确定这是一种很好的方法,但我得到了
GetMockShapes
方法,按照您所寻找的方式工作。这个想法是从 MockShapeFactory 开始,获取其 CreateMockShape 方法,将其转换为适当的通用版本并调用它来创建正确类型的对象。不过,它会获取一个
object
,而mockShapes
的Add
方法仅接受正确键入的Shape
。我不知道如何动态地将新的mockShape
转换为其适当的类型。我认为,无论如何,这都可以避免通过反射调用构建器的需要。相反,我绕过了类型检查系统(就像我说的,“不确定这是一种很好的做事方式”)。我从
mockShapes
列表开始,获取其运行时类型,获取其Add
方法,并使用新创建的对象调用该方法。编译器需要该方法的对象并允许这样做;反射强制在运行时进行正确的键入。如果 GetAppropriateMockType 返回不适当的类型,可能会发生不好的事情。更好的(但针对具体情况)方法
经过更多思考,我意识到这里有一个未声明的假设,您可以滥用。您正在尝试创建一个
List
并用TMock
填充它。TMock
的全部意义在于模拟TBase
,因此TMock
是一个TBase
。事实上,List
甚至使用TBase
作为其类型参数。这很重要,因为这意味着您不必将通用对象转换为
TMock
,只需将其转换为TBase
即可。由于TBase
在编译时已知,因此您可以使用简单的静态转换来将泛型对象传递给类型化方法,而不是绕过类型系统。我觉得这个方法如果能用的话会好很多。I'm not sure it's a very good way to do things, but I got the
GetMockShapes
method working the way you were looking for. The idea is to start with theMockShapeFactory
, get itsCreateMockShape
method, convert it to the appropriate generic version and invoke it to create an object of the correct type.That gets an
object
though, andmockShapes
'sAdd
method only accepts the correctly typedShape
. I couldn't figure out how to dynamically cast the newmockShape
to its appropriate type. That would have avoided the need to invoke the builder through reflection anyway, I think.I circumvented the type checking system instead (like I said, "not sure it's a very good way to do things"). I started with the
mockShapes
list, got its runtime type, got itsAdd
method, and invoked that with the newly created object. The compiler expects objects for the method and allows this; reflection enforces proper typing at runtime. Bad things might happen ifGetAppropriateMockType
ever returns an inappropriate type.A better (but situation-specific) way
After some more thinking I realized there's an unstated assumption here you can abuse. You're trying to make a
List<TBase>
and fill it withTMock
. The whole point ofTMock
is to impersonateTBase
, soTMock
is aTBase
. In fact, theList
even usesTBase
as its type parameter.That's important because it means you don't have to cast the generic object to a
TMock
, you can just cast it to aTBase
. SinceTBase
is known at compile time, you can use a simple static cast instead of circumventing the typing system to pass a generic object to a typed method. I think this way is a lot better if you can use it.