Кастинг с использованием универсального типа

1

Извинения, это длинное описание!

У меня есть общий класс, который представляет заданное значение.

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 реализует не общий интерфейс - значение должно быть доступно, но тип не может быть определен на уровне интерфейса. Может ли кто-нибудь объяснить это поведение?

Теги:
generics
casting
moq
nunit

1 ответ

1
Лучший ответ

Проблема здесь в том, что компилятор не знает, что вы намереваетесь дать ему интерфейс, который инструктирует его использовать другой метод 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, и вы получаете неверный/неожиданный результат.

  • 0
    Понимаю. При отладке я целую вечность не понимал, почему это так, но это имеет смысл. Какая боль. Спасибо за вашу помощь.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню