неявный оператор для универсальных типов

Есть ли что-то неправильное в использовании неявного оператора, подобного следующему:

//linqpad c# program example
void Main()
{
    var testObject = new MyClass<int>() { Value = 1 };

    var add = 10 + testObject; //implicit conversion to int here
    add.Dump(); // 11
}

class MyClass<T>
{
    public T Value { get; set; }
    public static implicit operator T (MyClass<T> myClassToConvert)
    {
        return myClassToConvert.Value;
    }
}

Я думал, что таким образом могу рассматривать экземпляр объекта как тип значения, но, видя, что я никогда не видел такого примера, я подумал, что, возможно, есть причина не делать что-то подобное что кто-то может указать?

В моем фактическом коде я думал сделать это как часть уровня абстракции данных, чтобы я мог возвращать объекты с информацией, описывающей базовые данные, но позволять логическому коду обрабатывать его как тип значения, когда все, что ему нужно знать о является значением, и в то же время сохраняйте все это красиво и безопасно с помощью дженериков.


person asawyer    schedule 29.09.2010    source источник


Ответы (2)


Если все следующее верно:

  • все возможные значения вашего типа MyClass<T> (включая null, если это не тип значения!) сопоставляются с допустимым значением T

  • неявный оператор никогда не выбрасывает (даже для null!)

  • неявное преобразование имеет семантический смысл и не сбивает с толку программиста-клиента

то в этом нет ничего плохого. Конечно, вы можете сделать любую из этих трех вещей, но это будет плохой дизайн. В частности, неявный оператор, который вызывает исключение, может быть очень трудно отлаживать, потому что место, где он вызывается, не говорит о том, что он вызывается.

Например, предположим, что T? не имеет неявного преобразования в T (где T, конечно, является типом значения). Если бы существовал такой неявный оператор, он должен был бы вызывать исключение, когда T? имеет значение null, поскольку нет очевидного значения для преобразования null в которое имело бы смысл для любого типа значения T.


Позвольте мне привести пример, когда у меня возникли проблемы с отладкой проблемы, когда неявный оператор выдал:

public string Foo()
{
    return some_condition ? GetSomething() : null;
}

Здесь GetSomething вернул что-то вроде написанного мной типа, который имеет определяемое пользователем неявное преобразование в string. Я абсолютно уверен, что GetSomething никогда не вернет null, и все же получил NullReferenceException! Почему? Поскольку приведенный выше код не эквивалентен

return some_condition ? (string)GetSomething() : (string)null;

но

return (string)(some_condition ? GetSomething() : (Something)null);

Теперь вы можете видеть, откуда взялось null!

person Timwi    schedule 29.09.2010
comment
Я не уверен, что понимаю озабоченность по поводу типов, допускающих значение NULL... Мне кажется, что если бы у вас было n = new MyClass‹int?›(){Value=null}; и потребляющий код попытался сделать что-то вроде int i=n; и это вызовет то же исключение, что и любое плохое приведение, здесь нет ничего особенного в типе, допускающем значение NULL, верно? - person asawyer; 29.09.2010
comment
@asawyer: Это был просто пример. Представьте, что T? не было, и вам пришлось реализовать Nullable<T> самостоятельно. Не могли бы вы дать ему неявное преобразование в T? Мой ответ объясняет, почему вы не должны этого делать и почему настоящий T? этого не делает. - person Timwi; 29.09.2010
comment
Теперь я вижу, что именно такие опасения вызвали вопрос в первую очередь. Похоже, я должен быть в порядке, чтобы продолжить, но будьте очень осторожны. - person asawyer; 29.09.2010

Это отличный образец. Просто имейте в виду, что для того, чтобы использовать его как переменную типа T, вы должны либо явно привести его к типу T, либо присвоить его переменной типа T. Приведение будет происходить автоматически в вызовах методов и других вещах (например, в вашем примере с добавлением), которые принимают T.

Неявное преобразование без присваивания?

person Matt Mills    schedule 29.09.2010
comment
@ Тимви, ты уверен? MyClass<T> myClass, на данный момент myClass.* будет разрешать только элементы на MyClass<T>, а не на T. Вам нужно будет сделать, как упоминает arootbeer, и сделать ((T)myClass).*, чтобы получить доступ к любым членам на самом T. - person Kirk Woll; 29.09.2010
comment
Я задавал этот вопрос буквально несколько дней назад. stackoverflow.com/questions/3703555/ - person Matt Mills; 29.09.2010
comment
@Кирк: я знаю. Это верно. Но утверждение в этом ответе по-прежнему неверно. Например, вы можете использовать MyClass<T> в вызове метода, который ожидает параметр T. Или в бинарном операторе, таком как пример в вопросе. Или в операторе return или yield return. Или или или... - person Timwi; 29.09.2010
comment
@Timwi - я отредактировал свое заявление, чтобы решить ваши проблемы с его правильностью. - person Matt Mills; 29.09.2010
comment
@Timwi, конечно, arootbeer было бы лучше сформулировать это так: вы должны либо явно привести его к T, либо использовать его там, где выводится T, например, переменная или параметр типа T. - person Kirk Woll; 29.09.2010
comment
@arootbeet - Вы правы, если неявный оператор не выполняется, вы получаете тип MyClass, а не T. Для меня это не проблема, так как в моем фактическом импиментировании тип оболочки предоставляет T, а также несколько дополнительных связанных свойств. - person asawyer; 29.09.2010
comment
Я знаю, что этот вопрос довольно старый, но как насчет ситуации, например, когда вы определяете класс SerializedKeyValuePair<TKey,TValue> и хотите иметь возможность неявно преобразовывать его в KeyValuePair<TKey,TValue> и из него? - person RWolfe; 23.03.2021
comment
@RWolfe, если я правильно понял ваш вопрос, ответ должен быть точно таким же. Существуют определенные места, где компилятор сделает это за вас, а кроме тех, которые вам придется либо явно привести к KeyValuePair<TKey,TValue>, либо явно введите переменную как KeyValuePair<TKey,TValue>. - person Matt Mills; 07.06.2021