Kiedy powinienem używać niejawnego operatora konwersji typu C #?

14

W języku C # możemy przeciążać niejawny operator konwersji w ten sposób (przykład z MSDN ):

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

W ten sposób możemy mieć typu, niestandardowy typ wartości , magicznie przekształca się w inny (niezwiązany) typ, pozostawiając widownię w oszołomieniu (dopóki nie zaglądają za kulisy i nie widzą ukrytego operatora konwersji).

Nie lubię pozostawiać w osłupieniu nikogo, kto czyta mój kod. Nie sądzę, że wiele osób tak robi.

Pytanie brzmi: jakie są przypadki użycia operatora konwersji typu domyślnego, który nie sprawi, że mój kod będzie trudniejszy do zrozumienia?

Mennice97
źródło
1
Łał. Nie wiedziałem, że to istnieje. Nie znaczy to, że korzystanie z niego jest koniecznie; Wiem, że ludzie bardzo się denerwowali z powodu tego rodzaju ukrywania funkcjonalności w C ++.
Katana314
@ Katana314: Nie to denerwowało ludzi, ale fakt, że ktoś dodaje przeciążenie (niezależnie od tego, czy jest to operator, funkcja konwersji, konstruktor, funkcja swobodna czy funkcja członka) o zaskakującym zachowaniu, najlepiej subtelnie zaskakującym.
Deduplicator
Polecam przeczytanie o „przeciążaniu operatora” w C ++, w szczególności operatorów „rzutowania”. Podejrzewam, że wiele takich samych argumentów za / przeciw jest takich samych, z tą różnicą, że debata trwa trzy razy, dopóki istnieje C # i jest o wiele więcej do przeczytania.

Odpowiedzi:

18

Poleciłbym jedynie niejawne konwersje między typami, które z grubsza przedstawiają te same wartości na różne sposoby. Na przykład:

  • Różne typy kolorystyczne podoba RGB, HSL, HSVi CMYK.
  • Różne jednostki dla tej samej wielkości fizycznej ( Metervs Inch).
  • Różne układy współrzędnych (biegunowy vs kartezjański).

Istnieją jednak pewne wytyczne, które wskazują na silne, gdy jest ona nie należy zdefiniować niejawna konwersja:

  • Jeśli konwersja powoduje znaczną utratę precyzji lub zasięgu, nie powinna być niejawna (np .: z float64 na float32 lub z long na int).
  • Jeśli konwersja może wygenerować InvalidCastwyjątek ( ), nie powinna być niejawna.
  • Jeśli konwersja powoduje alokację sterty za każdym razem, gdy jest wykonywana, nie powinna być niejawna.
  • Jeśli konwersja nie jest O(1) operacją, nie powinna być niejawna.
  • Jeśli typ źródła lub typ docelowy jest zmienny, konwersja nie powinna być niejawna.
  • Jeśli konwersja zależy od jakiegoś kontekstu (baza danych, ustawienia kultury, konfiguracja, system plików itp.), Nie powinna być niejawna (w tym przypadku odradzałbym również jawny operator konwersji).

Powiedzmy teraz, że operator konwersji f: T1 -> T2nie narusza żadnej z powyższych reguł, a następujące zachowanie zdecydowanie wskazuje, że konwersja może być niejawna:

  • Jeśli a == btakf(a) == f(b) .
  • Jeśli a != btakf(a) != f(b) .
  • Jeśli a.ToString() == b.ToString()takf(a).ToString() == f(b).ToString() .
  • Itd. Dla innych operacji, które są zdefiniowane zarówno na, jak T1i na T2.
Elian Ebbing
źródło
Wszystkie twoje przykłady są prawdopodobnie stratne. Czy i tak są wystarczająco dokładne, ...
Deduplicator
Tak, zdałem sobie sprawę, że :-). Nie mogłem wymyślić lepszego terminu „stratny”. Przez „straty” rozumiałem konwersje, w których zasięg lub precyzja zostały znacznie zmniejszone. Np. Z float64 na float32 lub z long na int.
Elian Ebbing
Myślę, że a! = B => f (a)! = F (b), prawdopodobnie nie powinien mieć zastosowania. Istnieje wiele funkcji, które mogą zwracać tę samą wartość dla różnych danych wejściowych, floor () i ceil (), na przykład po stronie matematycznej
cdkMoose
@cdkMoose Masz oczywiście rację i dlatego uważam te właściwości bardziej za „punkty bonusowe”, a nie zasady. Druga właściwość oznacza po prostu, że funkcja konwersji ma charakter iniekcyjny. Często dzieje się tak w przypadku konwersji na typ, który ma ściśle większy zakres, np. Od int32 do int64.
Elian Ebbing
@cdkMoose Z drugiej strony, pierwsza właściwość stwierdza po prostu, że dwie wartości w tej samej klasie równoważności T1(implikowane przez ==relację na T1) zawsze odwzorowują dwie wartości w tej samej klasie równoważności T2. Teraz, gdy o tym myślę, wydaje mi się, że pierwsza właściwość powinna być wymagana do niejawnej konwersji.
Elian Ebbing
6

Pytanie brzmi: jakie są przypadki użycia operatora konwersji typu domyślnego, który nie sprawi, że mój kod będzie trudniejszy do zrozumienia?

Gdy typy nie są niepowiązane (z programistami). Istnieją (rzadkie) scenariusze, w których masz dwa niepowiązane typy (w odniesieniu do kodu), które są faktycznie powiązane (w odniesieniu do domeny lub rozsądnych programistów).

Na przykład jakiś kod do dopasowania ciągów. Typowym scenariuszem jest dopasowanie dosłownego ciągu znaków. Zamiast wywoływania IsMatch(input, new Literal("some string")), niejawna konwersja pozwala pozbyć się tej ceremonii - szumu w kodzie - i skupić się na dosłowności łańcucha.

Większość programistów zobaczy IsMatch(input, "some string")i szybko zrozumie, co się dzieje. Sprawia, że ​​Twój kod jest wyraźniejszy w witrynie połączeń. Krótko mówiąc, nieco łatwiej jest zrozumieć, co się dzieje, przy niewielkim koszcie tego, co się dzieje.

Teraz możesz argumentować, że zwykłe przeciążenie funkcji, aby zrobić to samo, byłoby lepsze. I to jest. Ale jeśli tego rodzaju rzeczy są wszechobecne, to posiadanie jednej konwersji jest czystsze (mniej kodu, zwiększona spójność) niż wykonywanie stosu przeciążeń funkcji.

I możesz argumentować, że lepiej jest wymagać od programistów jawnego utworzenia typu pośredniego, aby zobaczyli „to, co się naprawdę dzieje”. To jest mniej proste. Osobiście uważam, że dosłowny przykład dopasowania łańcucha jest bardzo jasny na temat „tego, co naprawdę się dzieje” - programista nie musi znać mechaniki tego, jak wszystko się dzieje. Czy wiesz, jak cały Twój kod jest wykonywany przez różne procesory, na których działa Twój kod? Zawsze istnieje linia abstrakcji, w której programiści przestają dbać o to, jak coś działa. Jeśli uważasz, że niejawne kroki konwersji są ważne, nie używaj niejawnej konwersji. Jeśli uważasz, że to tylko ceremonia, aby komputer był szczęśliwy, a programiście lepiej byłoby nie widzieć tego hałasu wszędzie,

Telastyn
źródło
Twoja ostatnia uwaga może i powinna pójść jeszcze dalej: istnieje również linia, za którą programiści cholernie powinni lepiej nie dbać o to, jak coś się robi, ponieważ nie jest to umowne.
Deduplicator