„Stała poprawność” w C #

79

Celem stałej poprawności jest zapewnienie widoku instancji, której użytkownik nie może zmienić ani usunąć. Kompilator obsługuje to, wskazując, kiedy przerywasz stałość z poziomu funkcji stałej lub próbujesz użyć funkcji innej niż stała obiektu const. Więc bez kopiowania podejścia const, czy istnieje metodologia, której mogę użyć w C #, która ma te same końce?

Zdaję sobie sprawę z niezmienności, ale tak naprawdę nie przenosi się to na obiekty kontenera, by wymienić tylko jeden przykład.

tenpn
źródło
A co z implementacją const_castw C #?
kerem

Odpowiedzi:

64

Trafiłem też na ten problem wiele razy i skończyło się na korzystaniu z interfejsów.

Myślę, że ważne jest, aby porzucić pomysł, że C # jest dowolną formą, a nawet ewolucją C ++. To dwa różne języki, które mają prawie taką samą składnię.

Zwykle wyrażam `` stałą poprawność '' w C #, definiując widok klasy tylko do odczytu:

public interface IReadOnlyCustomer
{
    String Name { get; }
    int Age { get; }
}

public class Customer : IReadOnlyCustomer
{
    private string m_name;
    private int m_age;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        set { m_age = value; }
    }
}
Pułapka
źródło
12
Jasne, ale co, jeśli jedno z twoich pól jest listą lub typem rozszerzonym. Twoje rozwiązanie bardzo szybko się komplikuje.
Matt Cruikshank
12
@Trap: o to chodzi w pytaniu. Zwracanie kolekcji wewnętrznej jest złą praktyką, ponieważ świat zewnętrzny może modyfikować twoje wewnętrzne elementy, w C ++ rozwiązuje się to za pomocą const: zapewniasz stały widok kolekcji (nie możesz dodawać / usuwać elementów, oferuje tylko stały widok jej elementy) i jako taki jest bezpieczny (i powszechny idiom). Nie ma potrzeby opracowywania interfejsów ani innych sztuczek, aby uniknąć zmiany elementów wewnętrznych przez zewnętrzny kod.
David Rodríguez - dribeas
9
@Trap, zgadzam się, że mechanizm jest pomocą w rozwoju, który obejmuje wykrywanie błędów programistów, doświadczonych lub nie. Nie zgadzam się, że konieczność pisania interfejsów tylko do odczytu jest lepszym rozwiązaniem z kilku powodów. Pierwszą kwestią jest to, że za każdym razem, gdy piszesz klasę w C ++, pośrednio definiujesz stały interfejs: podzbiór zadeklarowanych elementów członkowskich const, bez dodatkowych kosztów definiowania oddzielnego interfejsu i wymagającego wysłania w czasie wykonywania. Oznacza to, że język zapewnia prosty sposób implementacji interfejsów stałych w czasie kompilacji.
David Rodríguez - dribeas
10
Ważne jest, aby zauważyć, że const-ness jest częścią projektu i deklaruje intencję tak samo wyraźnie, jak pisanie zewnętrznego interfejsu const. Ponadto dostarczanie interfejsów stałych może być złożonym zadaniem, jeśli musi być wykonywane ręcznie, i może wymagać napisania prawie tylu interfejsów, ile klas jest obecnych w typie złożonym. To prowadzi nas do twojego ostatniego zdania: „schrzań wszystko, bo zapomniałeś dodać const”. Łatwiej jest przyzwyczaić się do dodawania constwszędzie (koszt rozwoju jest niewielki) niż do pisania interfejsów tylko do odczytu dla każdej klasy.
David Rodríguez - dribeas
9
Jednym z powodów, dla których naprawdę polubiłem C #, kiedy wyszło, było to, że nie wyrzucił wszystkich pragmatycznych funkcji C ++, tak jak zrobiła to Java. Java próbowała robić wszystko za pomocą interfejsów i była to katastrofa. Dowód jest w budyniu. Z biegiem czasu wiele funkcji zostało „z powrotem” dodanych do Javy, które pierwotnie zostały uznane za herezję. Projektowanie oparte na interfejsach ma swoje miejsce, ale doprowadzenie do skrajności może niepotrzebnie skomplikować architekturę programu. W rezultacie spędzasz więcej czasu na pisaniu standardowego kodu, a mniej na wykonywaniu przydatnych rzeczy.
kgriffs
29

Aby skorzystać z zalet const-szaleństwa (lub czystości w kategoriach programowania funkcjonalnego), będziesz musiał zaprojektować swoje klasy w taki sposób, aby były niezmienne, tak jak klasa String języka c #.

Takie podejście jest o wiele lepsze niż zwykłe oznaczanie obiektu jako tylko do odczytu, ponieważ dzięki niezmiennym klasom można łatwo przekazywać dane w środowiskach wielozadaniowych.

Sam
źródło
8
ale niezmienność tak naprawdę nie jest skalowana do złożonych obiektów, czy nie?
tenpn
8
Twierdzę, że gdyby twój obiekt był tak złożony, że niezmienność była niemożliwa, miałbyś dobrego kandydata do refaktoryzacji.
Jim Burger,
2
Myślę, że ten jest najlepszy z całej grupy. Niezmienne obiekty są używane zbyt rzadko.
3
Nie tylko to, ale są przypadki, w których chcesz zmienić obiekty (obiekty się zmieniają!), Ale w większości przypadków nadal oferujesz widok tylko do odczytu. Niezmienne obiekty oznaczają, że za każdym razem, gdy musisz dokonać zmiany (nastąpi zmiana), będziesz musiał utworzyć nowy obiekt ze wszystkimi tymi samymi danymi oprócz zmiany. Pomyśl o szkole, w której są sale szkolne, uczniowie ... czy chcesz tworzyć nową szkołę za każdym razem, gdy mija data urodzenia ucznia i zmienia się jej wiek? A może możesz po prostu zmienić wiek na poziomie ucznia, ucznia na poziomie sali, może pokoju na poziomie szkoły?
David Rodríguez - dribeas
8
@tenpn: Niezmienność faktycznie skaluje się niewiarygodnie dobrze, jeśli jest wykonywana prawidłowo. Jedyną rzeczą, która naprawdę pomaga, jest użycie Builderklas dla dużych typów niezmiennych (Java i .NET definiują StringBuilderklasę, co jest tylko jednym przykładem).
Konrad Rudolph
24

Chciałem tylko zauważyć, że wiele kontenerów System.Collections.Generics ma metodę AsReadOnly, która zwraca niezmienną kolekcję.

Rick Minerich
źródło
4
Nadal występuje problem polegający na tym, że Ts są zmienne, nawet jeśli ReadOnlyCollection <T> nie jest.
arynaq
4

C # nie ma takiej funkcji. Możesz przekazać argument według wartości lub przez odwołanie. Samo odwołanie jest niezmienne, chyba że określisz modyfikator ref . Ale dane, do których istnieją odniesienia, nie są niezmienne. Musisz więc uważać, jeśli chcesz uniknąć skutków ubocznych.

MSDN:

Przekazywanie parametrów

aku
źródło
Cóż, myślę, że do tego właśnie sprowadza się pytanie: jaki jest najlepszy sposób na uniknięcie skutków ubocznych braku struktury const?
tenpn
Niestety, mogą w tym pomóc tylko typy niezmienne. Możesz rzucić okiem na Spec # - jest kilka interesujących sprawdzeń czasu kompilacji.
aku
3

Rozwiązaniem są interfejsy, które w rzeczywistości mają większe możliwości niż „const” w C ++. const to uniwersalne rozwiązanie problemu, w którym „stała” jest zdefiniowana jako „nie ustawia elementów ani nie wywołuje czegoś, co ustawia elementy”. To dobry skrót na określenie ciągłości w wielu scenariuszach, ale nie we wszystkich. Na przykład rozważmy funkcję, która oblicza wartość na podstawie niektórych elementów członkowskich, ale także buforuje wyniki. W C ++ jest to uważane za niestałe, chociaż z punktu widzenia użytkownika jest to zasadniczo stała.

Interfejsy zapewniają większą elastyczność w definiowaniu określonego podzbioru możliwości, które chcesz udostępnić w swojej klasie. Chcesz być konsekwentny? Po prostu zapewnij interfejs bez metod mutowania. Chcesz zezwolić na ustawienie niektórych rzeczy, a innych nie? Zapewnij interfejs tylko z tymi metodami.

hojny
źródło
15
Nie w 100% poprawne. Metody const C ++ mogą mutować elementy członkowskie oznaczone jako mutable.
Constantin,
3
Słusznie. Rzutowanie const pozwala pozbyć się ciągłości. Oba rodzaje sugerują, że nawet projektanci C ++ zdawali sobie sprawę, że jeden rozmiar dla wszystkich to naprawdę jeden rozmiar dla większości.
wspaniały,
8
Zaletą C ++ jest to, że w większości przypadków masz const i możesz implementować interfejsy dla innych. Teraz, oprócz const, C ++ ma słowo kluczowe mutable, które można zastosować do atrybutów jako pamięci podręczne danych lub mechanizmy blokujące (muteksy itp.). Const nie „nie zmieni żadnego wewnętrznego atrybutu), ale raczej nie zmieni stanu obiektu postrzeganego z zewnątrz. Oznacza to, że każde użycie obiektu przed i po wywołaniu metody const da ten sam wynik.
David Rodríguez - dribeas
9
odrzucenie const w celu zmutowania komety w C ++ jest zachowaniem nie-diabła (demony nosowe). const_cast służy do łączenia się ze starszym kodem, który będąc logicznie stałą, nie jest oznaczony jako const.
jk.
3

Zgadzam się z niektórymi innymi, rozważ użycie pól tylko do odczytu, które zainicjujesz w konstruktorze, aby utworzyć niezmienne obiekty.

    public class Customer
    {
    private readonly string m_name;
    private readonly int m_age;

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
    }

    public int Age
    {
        get { return m_age; }
    }
  }

Alternatywnie możesz również dodać zakres dostępu do właściwości, tj. Publiczny zestaw get i protected?

    public class Customer
    {
    private string m_name;
    private int m_age;

    protected Customer() 
    {}

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
        protected set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        protected set { m_age = value; }
    }
  }
Stuart McConnell
źródło
3
Są to różne podejścia, które tak naprawdę nie pasują do całego problemu. Na poziomie kontroli dostępu zezwalasz tylko na wyprowadzanie klas ze zmian, w wielu przypadkach nie spowoduje to odpowiedniego modelowania świata rzeczywistego. Nauczyciel nie wywodzi z akt ucznia, ale może chcieć zmienić oceny uczniów, chociaż uczeń nie może zmienić ocen, ale może je przeczytać ... żeby podać prosty przykład.
David Rodríguez - dribeas
2
  • Const słowo kluczowe może być używany do kompilacji stałych czasowych, takich jak prymitywnych typów i ciągów
  • Readonly kluczowe mogą być wykorzystane dla stałych run-time, takich jak typów referencyjnych

Problem z readonly polega na tym, że pozwala on tylko na stałe odniesienie (wskaźnik). Rzecz, do której się odwołuje (wskazywana) może być nadal modyfikowana. To jest trudna część, ale nie da się tego obejść. Zaimplementowanie stałych obiektów oznacza, że ​​nie ujawniają one żadnych modyfikowalnych metod ani właściwości, ale jest to niewygodne.

Zobacz także Efektywny C #: 50 konkretnych sposobów ulepszania języka C # (pozycja 2 - Preferuj tylko do odczytu do stałej).

Thomas Bratt
źródło