Извинения, это длинное описание!
У меня есть общий класс, который представляет заданное значение.
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
что 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
и, следовательно, не будет вызывать этот метод через интерфейс. Объект Mock, созданный Moq, создает явную реализацию ToString
и не переопределяет тот, который унаследован от System.Object
, и вы получаете неверный/неожиданный результат.