使用泛型类型强制转换模拟

本文关键字:转换 模拟 泛型类型 | 更新日期: 2023-09-27 18:05:27

抱歉,这是一个很长的描述!

我有一个表示给定值的泛型类。

public class ValueClass<T>
{
    public object Value { get { return this._value; } }
    protected T _value;
    public ValueClass(T value)
    {
        this._value = value;
    }
    public string Print()
    {
        return ((T)this.Value).ToString();
    }
}

可以这样执行:

[TestCase(1, "1")]
[TestCase(2, "2")]
public void Works(int value, string expected)
{
    ValueClass<int> uut = new ValueClass<int>(value);
    string ret = uut.Print();
    Assert.AreEqual(expected, ret);
}

对于像int这样的类型,这工作得很好,但是如果我想使用自定义类,那么这就失败了。例如,对于类型ICustomType,应该调用ToString方法。

public interface ICustomType
{
    string ToString();
}

所以下面的测试失败,其中ICustomType被模拟:

[TestCase("1")]
[TestCase("2")]
public void Fails(string expected)
{
    Mock<ICustomType> customTypeStub = new Mock<ICustomType>();
    customTypeStub.Setup(x => x.ToString()).Returns(expected);
    ValueClass<ICustomType> uut = new ValueClass<ICustomType>(customTypeStub.Object);
    string ret = uut.Print();
    Assert.AreEqual(expected, ret);
}

(下面添加了额外的诊断行-将其转换为特定类型的作品,但不转换为T类型)

public class ValueClass<T>
{
    public object Value { get { return this._value; } }
    protected T _value;
    public ValueClass(T value)
    {
        this._value = value;
    }
    public string Print()
    {
        Console.WriteLine("this.Value.ToString() : " + this.Value.ToString());
        Console.WriteLine("((ICustomType)this.Value).ToString() : " + ((ICustomType)this.Value).ToString());
        Console.WriteLine("((T)this.Value).ToString() : " + ((T)this.Value).ToString());
        Console.WriteLine("typeof(T) : " + typeof(T));
        Console.WriteLine("(typeof(T) == typeof(ICustomType)) : " + (typeof(T) == typeof(ICustomType)));
        return ((T)this.Value).ToString();
    }
}

以下诊断信息:

***** tests.Types.Fails("1")
this.Value.ToString() : Castle.Proxies.ICustomTypeProxy
((T)this.Value).ToString() : Castle.Proxies.ICustomTypeProxy
typeof(T) : Types.ICustomType
(typeof(T) == typeof(ICustomType)) : True
***** tests.Types.Fails("2")
this.Value.ToString() : Castle.Proxies.ICustomTypeProxy
((T)this.Value).ToString() : Castle.Proxies.ICustomTypeProxy
typeof(T) : Types.ICustomType
(typeof(T) == typeof(ICustomType)) : True

就我所知,Moq正确地模拟了ToString方法。当手动转换为固定类型时,这工作得很好。但是,当依赖于泛型类型T来定义强制转换时,这就失败了。

请注意,我必须保持Value作为类型object而不是类型T的原因是ValueClass实现了一个非泛型接口-值必须是可访问的,但类型不能在接口级别定义。有人能解释一下这种行为吗?

使用泛型类型强制转换模拟

这里的问题是编译器不知道你打算给它一个接口,指示它使用一个不同于每个对象的 ToString方法。

编译器唯一知道的是T某种类型。编译器将在编译时使用它所拥有的知识编译该方法,即使您稍后给它一个接口,实际上告诉它使用不同的ToString方法,它也不会使用它,因为它已经为所有类型编译了该方法,并且该编译使用了System.Object提供的方法。

所以,你不能这样做。

可以指示你的ValueClass只支持T实现你的接口的类型,但我怀疑这不是你想要的。

下面是Print方法的编译过程:
ValueClass`1.Print:
IL_0000:  ldarg.0     
IL_0001:  call        15 00 00 0A 
IL_0006:  unbox.any   05 00 00 1B 
IL_000B:  stloc.0     // CS$0$0000
IL_000C:  ldloca.s    00 // CS$0$0000
IL_000E:  constrained. 05 00 00 1B 
IL_0014:  callvirt    System.Object.ToString
IL_0019:  ret         

如您所见,它被编译为直接调用System.Object.ToString,显然您可以在提供给T的实际类型中覆盖,但是编译器不理解您在某些情况下打算给它一个带有自己的ToString方法的接口,因此不会通过接口调用方法。Moq创建的Mock对象创建了ToString的显式实现,并且没有覆盖从System.Object继承的对象,因此您会得到不正确/意外的结果。