2016-11-24 10 views
7

Utworzono niestandardową strukturę do reprezentowania kwoty. Jest to w zasadzie opakowanie o numerze decimal. Ma niejawnego operatora konwersji, który przesłał go z powrotem do decimal.Dlaczego metoda Assert.AreEqual na niestandardowej strukturze z niejawnym operatorem konwersji kończy się niepowodzeniem?

W teście jednostkowym potwierdzam, że kwota jest równa oryginalnej wartości dziesiętnej, ale test kończy się niepowodzeniem.

[TestMethod] 
public void AmountAndDecimal_AreEqual() 
{ 
    Amount amount = 1.5M; 

    Assert.AreEqual(1.5M, amount); 
} 

Gdy używam int choć (dla których nie stworzyć operatorowi konwersji), test ma sukces.

[TestMethod] 
public void AmountAndInt_AreEqual() 
{ 
    Amount amount = 1; 

    Assert.AreEqual(1, amount); 
} 

Kiedy najechaniu AreEqual pokazuje, że pierwszy z nich postanawia

public static void AreEqual(object expected, object actual); 

a druga prowadzi do

public static void AreEqual<T>(T expected, T actual); 

Wygląda wartości int1 jest niejawnie rzutowany na Amount, a wartość decimal nie jest równa 1.5M.

Nie rozumiem, dlaczego tak się dzieje. Spodziewałbym się czegoś wręcz przeciwnego. Pierwszy test jednostkowy powinien być w stanie obsłużyć decimal do .

Po dodaniu niejawnej obsady do int (co nie ma sensu), drugi test jednostkowy również nie powiedzie się. Zatem dodanie niejawnego operatora rzuca test jednostkowy.

Mam dwa pytania:

  1. Jakie jest wytłumaczenie tego zachowania?
  2. Jak mogę naprawić strukturę Amount, aby oba testy zakończyły się pomyślnie?

(wiem, że mogę zmienić test zrobić wyraźną przemianę, ale jeśli nie absolutnie trzeba, nie będę)

Moje Kwota struct (wystarczy minimalny wdrożenia do pokazać problem)

public struct Amount 
{ 
    private readonly decimal _value; 

    private Amount(decimal value) 
    { 
     _value = value; 
    } 

    public static implicit operator Amount(decimal value) 
    { 
     return new Amount(value); 
    } 

    public static implicit operator decimal(Amount amount) 
    { 
     return amount._value; 
    } 
} 
+1

Twoi operatorzy są "niejawni". Więc czy powinno to być 'AreEqual ' lub 'AreEqual '? – PetSerAl

+1

@PetSerAl ma rację. jeśli użyjesz 'AreEqual ' lub 'AreEqual ' to przejdzie. – Nkosi

Odpowiedz

6

Złe rzeczy dzieją się, kiedy można konwertować implicit ly w obu kierunkach, a to jest jeden z przykładów.

powodu ukrytych konwersji kompilator jest w stanie odebrać Assert.AreEqual<decimal>(1.5M, amount); i Assert.AreEqual<Amount>(1.5M, amount); o równej wartości. *

Ponieważ są one równe, ani przeciążenia będą zbierane przez wnioskowanie.

Ponieważ nie ma przeciążenia do wyboru przez wnioskowanie, żadne z nich nie pojawia się na liście do wyboru najlepszego dopasowania i dostępny jest tylko formularz (object, object). To ten, który został wybrany.

Z Assert.AreEqual(1, amount) ówczesnego ponieważ istnieje niejawna konwersja z int do Amount (przez niejawny INT> przecinku), ale nie niejawna konwersja z Amount do int kompilator myśli „oczywiście one oznaczać Assert.AreEqual<Amount>() tutaj” †, a więc jest doborowy.

Można wyraźnie podnieść przeciążenie z Assert.AreEqual<Amount>() lub Assert.AreEqual<decimal>() ale jesteś prawdopodobnie lepiej czyni jedną z konwersji z „zwężenie” od tego musi być explicit jeśli w ogóle możliwe, ponieważ ta cecha swojej struktury zaszkodzi jeszcze raz . (Hurra dla testów jednostkowych znajdowanie wad).


* Kolejny ważny wybór przeciążenie jest wybranie Assert.AreEqual<object>, ale to nigdy nie odebrał przez wnioskowanie, ponieważ:

  1. Zarówno odrzucone przeciążenia zostały uznane nawet lepiej.
  2. Zawsze jest pobity przez nietypową formę, która i tak zajmuje object.

Jako taki może zostać wywołany tylko przez włączenie <object> w kodzie.

† Kompilator traktuje wszystko, co do niego powiedziane, jako oczywiste lub całkowicie niezrozumiałe. Są tacy ludzie.

+0

Dziękuję za wyczerpującą odpowiedź. W moim przypadku "niejawne" oddane z powrotem do "dziesiętnego" nie było potrzebne, więc zmieniłem je na "jawne". Zastanawiam się jednak, jak mogłem sam się dowiedzieć, dlaczego wywoływany jest AreEqual (obiekt, obiekt). Nie ma błędów kompilatora, ale po prostu nie robię tego, czego się spodziewałem. Wszelkie wskazówki na ten temat? Czy potrafisz w jakiś sposób zobaczyć drzewo decyzyjne dla kompilatora? – comecme

+1

@comecme jeśli jestem zdezorientowany przez wybór przeciążenia najpierw napiszę kilka metod, które pasują do podpisu (więc tutaj stworzyłem może 'AssertAreEqual (obiekt x, obiekt y)' i 'AssertAreEqual (T x, T y) '. Następnie potwierdź, że otrzymuję to samo zachowanie (formularz obiektu jest zajęty), a następnie usuń wybrane i zobacz, co się stanie:' CS0411 Argumentów typu dla metody "AssertAreEqual (T, T)" nie można wywodzić z Spróbuj jawnie określić argumenty typu. "Zastanawianie się, dlaczego nie można ich wywnioskować, byłoby wielką wskazówką. Teraz, kiedy idę, aby określić jednoznacznie, mam pytanie ... –

+0

... powinienem wyraźnie powiedzieć" "lub jawnie powiedzieć ''? A ha! Nie wiem, więc w jaki sposób kompilator? Identyfikator problemu.Jednak wielkim problemem jest to, że jestem świeżą parą oczu patrząc na twój kod bez planu na to w umysł w co sprawia, że ​​ten ostatni skok mentalny jest łatwiejszy do wykonania. Gdy jest to własny kod, ma się na myśli plan i może być trudniej zrozumieć, dlaczego to, co jest oczywiste dla osoby, która uczyniła ten plan, nie jest dla kompilatora. Jedyne, co można zrobić, to pójść i wypić filiżankę kawy, pójść na spacer i wrócić do niej, by uzyskać świeży wygląd. –

Powiązane problemy