使用泛型类型转换模拟

时间:2014-05-27 14:02:10

标签: c# generics casting nunit moq

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

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

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实现了非泛型接口 - 值必须可访问但类型无法定义在接口级别。 谁能解释这种行为?

1 个答案:

答案 0 :(得分:1)

这里的问题是编译器不知道你打算给它一个接口来指示它使用不同的 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继承的实现,因此您会得到错误/意外的结果。