Когда мне следует использовать оператор неявного преобразования типов в C #?

14

В C # мы можем перегрузить оператор неявного преобразования следующим образом (пример из MSDN ):

struct Digit
{
    /* ... */
    public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
    {
        /* ... */
    }
}

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

Я не люблю оставлять кого-то, кто читает мой код в недоумении. Я не думаю, что многие люди делают.

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

Mints97
источник
1
Вау. Я на самом деле не знал, что это существовало. Не то, чтобы это обязательно было хорошей вещью для использования; Я знаю, что люди были очень раздражены подобным сокрытием функциональности в C ++.
Katana314
@ Katana314: Это было не то, что раздражало людей, а то, что кто-то добавил перегрузку (будь то оператор, функция преобразования, конструктор, свободная функция или функция-член) с удивительным поведением, предпочтительно слегка удивительным.
дедупликатор
Я рекомендую вам ознакомиться с «перегрузкой операторов» в C ++, в частности, с операторами «приведения». Я подозреваю, что многие из аргументов за / против одинаковы, за исключением того, что дебаты продолжались три раза, пока существовал C # с гораздо большим объемом информации для чтения.

Ответы:

18

Я бы рекомендовал неявные преобразования между типами, которые по-разному представляют одни и те же значения. Например:

  • Различные виды цвета , как RGB, HSL, HSVи CMYK.
  • Различные единицы для одной и той же физической величины ( Meterпротив Inch).
  • Различные системы координат (полярные и декартовы).

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

  • Если преобразование вызывает значительную потерю точности или диапазона, то оно не должно быть неявным (например: от float64 до float32 или от long до int).
  • Если преобразование может InvalidCastвызвать исключение ( ), то оно не должно быть неявным.
  • Если преобразование вызывает выделение кучи каждый раз, когда оно выполняется, то оно не должно быть неявным.
  • Если преобразование не является O(1)операцией, то оно не должно быть неявным.
  • Если тип источника или целевой тип является изменчивым, преобразование не должно быть неявным.
  • Если преобразование зависит от некоторого контекста (базы данных, настроек культуры, конфигурации, файловой системы и т. Д.), То оно не должно быть неявным (в этом случае я бы также не рекомендовал явный оператор преобразования).

Теперь предположим, что ваш оператор преобразования f: T1 -> T2не нарушает ни одно из вышеприведенных правил, тогда следующее поведение убедительно показывает, что преобразование может быть неявным:

  • Если a == bтогдаf(a) == f(b) .
  • Если a != bтогдаf(a) != f(b) .
  • Если a.ToString() == b.ToString()тогдаf(a).ToString() == f(b).ToString() .
  • И т.д. для других операций, которые определены на обоих T1и T2.
Элиан Эббинг
источник
Все ваши примеры, вероятно, с потерями. Являются ли они достаточно точными в любом случае, ...
Дедупликатор
Да, я понял это :-). Я не мог придумать лучшего термина для «с потерями». Под «потерями» я понимал преобразования, в которых диапазон или точность существенно уменьшены. Например, от float64 до float32 или от long до int.
Элиан Эббинг
Я думаю, что a! = B => f (a)! = F (b), вероятно, не должно применяться. Существует множество функций, которые могут возвращать одно и то же значение для разных входных данных, например floor () и ceil (), например, со стороны математики
cdkMoose,
@cdkMoose Вы, конечно, правы, и поэтому я вижу эти свойства скорее как «бонусные баллы», а не правила. Второе свойство просто означает, что функция преобразования инъективна. Это часто имеет место, когда вы конвертируете в тип, который имеет строго больший диапазон, например от int32 до int64.
Элиан Эббинг
@cdkMoose С другой стороны, первое свойство просто утверждает, что два значения в одном и том же классе эквивалентности T1(подразумеваемом ==отношением on T1) всегда отображаются на два значения в одном и том же классе эквивалентности T2. Теперь, когда я думаю об этом, я предполагаю, что первое свойство на самом деле должно требоваться для неявного преобразования.
Элиан Эббинг
6

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

Когда типы не связаны (с программистами). Есть (редкие) сценарии, когда у вас есть два не связанных типа (с точки зрения кода), которые на самом деле связаны (с точки зрения предметной области или разумных программистов).

Например, некоторый код для сопоставления строк. Обычный сценарий - сопоставление строкового литерала. Вместо вызова IsMatch(input, new Literal("some string"))неявное преобразование позволяет вам избавиться от этой церемонии - шума в коде - и сосредоточиться на строковом литерале.

Большинство программистов увидят IsMatch(input, "some string")и быстро поймут, что происходит. Это делает ваш код более понятным на сайте вызова. Короче говоря, это немного облегчает понимание того, что происходит, за небольшую цену того, как это происходит.

Теперь вы можете утверждать, что простая перегрузка функции для выполнения того же действия была бы лучше. И это. Но если такого рода вещи встречаются повсеместно, то одно преобразование будет чище (меньше кода, повышенная согласованность), чем выполнение множества перегрузок функций.

И вы можете возразить, что лучше потребовать от программистов явного создания промежуточного типа, чтобы они видели «что на самом деле происходит». Это не так просто. Лично я думаю, что пример буквального совпадения строк очень ясно говорит о «что происходит на самом деле» - программисту не нужно знать механизм того, как все происходит. Знаете ли вы, как весь ваш код выполняется различными процессорами, на которых он работает? Всегда есть линия абстракции, где программисты перестают заботиться о том, как что-то работает. Если вы считаете, что шаги неявного преобразования важны, не используйте неявное преобразование. Если вы думаете, что это просто церемония, чтобы сохранить компьютер счастливым, и программисту было бы лучше не видеть этот шум повсюду,

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