Używam programu Visual Studio 2010 + Resharper i wyświetla ostrzeżenie dotyczące następującego kodu:
if (rect.Contains(point))
{
...
}
rect
to readonly Rectangle
pole, a Resharper pokazuje mi to ostrzeżenie:
„Nieczysta metoda jest wywoływana dla pól tylko do odczytu typu wartości”.
Jakie są nieczyste metody i dlaczego jest mi pokazywane to ostrzeżenie?
Odpowiedzi:
Po pierwsze, odpowiedzi Jona, Michaela i Jareda są zasadniczo poprawne, ale mam jeszcze kilka rzeczy, które chciałbym do nich dodać.
Łatwiej jest scharakteryzować czyste metody. „Czysta” metoda ma następujące cechy:
Na przykład
Math.Cos
to czysta metoda. Jego wyjście zależy tylko od jego wejścia, a wejście nie jest zmieniane przez wywołanie.Nieczysta metoda to metoda, która nie jest czysta.
Przychodzą mi do głowy dwie. Pierwszą jest ta wskazana przez Jona, Michaela i Jareda i przed nią ostrzega cię Resharper. Kiedy wywołujesz metodę na strukturze, zawsze przekazujemy odniesienie do zmiennej, która jest odbiornikiem, na wypadek gdyby metoda chciała zmutować zmienną.
A co, jeśli wywołasz taką metodę na wartości, a nie na zmiennej? W takim przypadku tworzymy zmienną tymczasową, kopiujemy do niej wartość i przekazujemy referencję do zmiennej.
Zmienna tylko do odczytu jest uważana za wartość, ponieważ nie można jej mutować poza konstruktorem. Więc kopiujemy zmienną do innej zmiennej, a nieczysta metoda prawdopodobnie powoduje mutację kopii, jeśli zamierzasz zmutować zmienną.
Na tym polega niebezpieczeństwo przekazania struktury tylko do odczytu jako odbiorcy . Istnieje również niebezpieczeństwo przekazania struktury zawierającej pole tylko do odczytu. Struktura zawierająca pole tylko do odczytu jest powszechną praktyką, ale zasadniczo polega ona na wypisywaniu czeku, że system typów nie ma środków do zrealizowania; „Tylko do odczytu” konkretnej zmiennej określa właściciel magazynu. Instancja typu referencyjnego „posiada” własną pamięć, ale instancja typu wartościowego nie!
struct S { private readonly int x; public S(int x) { this.x = x; } public void Badness(ref S s) { Console.WriteLine(this.x); s = new S(this.x + 1); // This should be the same, right? Console.WriteLine(this.x); } }
Wydaje się, że
this.x
to się nie zmieni, ponieważ x jest polem tylko do odczytu iBadness
nie jest konstruktorem. Ale...S s = new S(1); s.Badness(ref s);
... jasno pokazuje, że to fałsz.
this
is
odnoszą się do tej samej zmiennej, a ta zmienna nie jest tylko do odczytu!źródło
struct Id {
private readonly int _id;
public Id(int id) { _id = id; }
public int ToInt() => _id;
}
Dlaczego ToInt jest nieczyste?return
. Na tej podstawie zgaduję, że jedynym kryterium jest to, czy metoda ma[Pure]
atrybut.rect
. Czy mówimy, że kopiarect
jest przekazywana doContains
metody?Nieczysta metoda to taka, która nie gwarantuje pozostawienia wartości tak, jak była.
W .NET 4 możesz dekorować metody i typy,
[Pure]
aby zadeklarować je jako czyste, a R # zwróci na to uwagę. Niestety, nie możesz zastosować go do innych członków i nie możesz przekonać R #, że typ / element członkowski jest czysty w projekcie .NET 3.5, o ile wiem. (To gryzie mnie cały czas w Noda Time .)Idea jest taka, że jeśli wywołanie metody, która mutuje zmienna, ale to nazwać na pole tylko do odczytu, to chyba nie robi tego, co chcesz, więc R # ostrzeże Cię o tym. Na przykład:
public struct Nasty { public int value; public void SetValue() { value = 10; } } class Test { static readonly Nasty first; static Nasty second; static void Main() { first.SetValue(); second.SetValue(); Console.WriteLine(first.value); // 0 Console.WriteLine(second.value); // 10 } }
Byłoby to naprawdę użyteczne ostrzeżenie, gdyby każda metoda, która była rzeczywiście czysta, została zadeklarowana w ten sposób. Niestety tak nie jest, więc jest wiele fałszywych alarmów :(
źródło
JetBrains.Annotations.PureAttribute
zamiastSystem.Diagnostics.Contracts.PureAttribute
, mają to samo znaczenie dla analizy kodu ReSharper i powinny działać tak samo na .NET 3.5, .NET 4 lub Silverlight. Możesz również zewnętrznie dodawać adnotacje do zespołów, których nie jesteś właścicielem, używając plików XML (spójrz na katalog ExternalAnnotations w ścieżce bin ReSharper), może to być naprawdę przydatne!System.Diagnostics.Contracts.PureAttribute
nie usunęło tego ostrzeżenia w R # 8.2, podczas gdyJetBrains.Annotations.PureAttribute
tak było. Te dwa atrybuty mają również różne opisy:Pure
Atrybut kontraktów implikuje „wynik zależy tylko od parametrów”, podczas gdy JetBrainsPure
sugeruje „nie powoduje widocznych zmian stanu” bez wykluczania stanu obiektu używanego do obliczenia wyniku. (Ale nadal kontrakty, którePure
nie mają takiego samego wpływu na to ostrzeżenie, prawdopodobnie są błędem.)Krótka odpowiedź brzmi, że jest to fałszywie pozytywny wynik i możesz bezpiecznie zignorować ostrzeżenie.
Dłuższą odpowiedzią jest to, że dostęp do typu wartości tylko do odczytu tworzy jego kopię , tak że wszelkie zmiany wartości wprowadzone przez metodę wpływałyby tylko na kopię. ReSharper nie zdaje sobie sprawy, że
Contains
jest to czysta metoda (co oznacza, że nie ma skutków ubocznych). Eric Lippert mówi o tym tutaj: Mutating Readonly Structsźródło
private readonly SpinLock _spinLock = new SpinLock();
- taka blokada będzie całkowicie bezużyteczny (od readonly modyfikujących przyczyn on-the-fly kopia być tworzone za każdym razem Wprowadź metodę nazywa się na nim)Wygląda na to, że Reshaprer wierzy, że metoda
Contains
może zmienićrect
wartość. Ponieważrect
jestreadonly struct
kompilatorem C #, który tworzy kopie obronne wartości, aby zapobiec mutowaniureadonly
pola przez metodę . Zasadniczo końcowy kod wygląda następującoRectangle temp = rect; if (temp.Contains(point)) { ... }
Resharper ostrzega cię tutaj, że
Contains
może mutowaćrect
w sposób, który zostałby natychmiast utracony, ponieważ zdarzyło się to tymczasowo.źródło
Metoda zanieczyszczona to metoda, która może mieć skutki uboczne. W tym przypadku Resharper wydaje się sądzić, że może się to zmienić
rect
. Prawdopodobnie nie, ale łańcuch dowodów jest zerwany.źródło