Czy parametry mogą być stałe?

85

Szukam odpowiednika języka Java w C # final. Czy to istnieje?

Czy C # ma coś podobnego do następującego:

public Foo(final int bar);

W powyższym przykładzie barjest zmienną tylko do odczytu i nie można jej zmienić Foo(). Czy jest jakiś sposób, aby to zrobić w C #?

Na przykład, może mam długą metodę, która będzie pracować z x, yi zwspółrzędne jakiegoś przedmiotu (int). Chcę mieć absolutną pewność, że funkcja w żaden sposób nie zmienia tych wartości, uszkadzając w ten sposób dane. Dlatego chciałbym zadeklarować je tylko do odczytu.

public Foo(int x, int y, int z) {
     // do stuff
     x++; // oops. This corrupts the data. Can this be caught at compile time?
     // do more stuff, assuming x is still the original value.
}
Nick Heiner
źródło
Duplikat stackoverflow.com/questions/2125591/… (i ma świetną odpowiedź od Erica Lipperta, btw)
casperOne
12
Nie sądzę, żeby był to dokładny duplikat. To pytanie dotyczy różnicy między przekazywaniem przez odniesienie a przekazywaniem przez wartość. Myślę, że Rosarch mógł użyć złego przykładowego kodu w punkcie, który próbował przekazać.
Corey Sunwold
@Rosarch: Z tego, co rozumiem ze słowa „finał”, wynika, że ​​nie możesz wykonywać większej akcji z obiektem, cokolwiek by to nie było. Rozumiem, że „ostateczne” zastosowane do klasy byłoby równoważne z „zapieczętowanym” w C #. Ale jaka jest korzyść lub rzeczywiste użycie tego słowa kluczowego „ostateczne”? Widziałem to kiedyś prawie w każdym miejscu w kodzie źródłowym JAVA.
Will Marcouiller
3
@Will Marcouiller Z końcowym słowem kluczowym nie jest to obiekt, którego nie można zmienić, ponieważ można użyć metody, która zmienia elementy wewnętrzne obiektu, jest to odniesienie do obiektu, którego nie można zmienić. W przypadku przekazania przez wartość, tak jak w podanym przykładzie, zapobiegnie to, że x ++ nie będzie prawidłową operacją, ponieważ wartość będzie się zmieniać. Zaletą byłoby to, że otrzymujesz kontrolę poprawności czasu kompilacji, aby upewnić się, że nic nie ustawia innej wartości. Jednak z mojego doświadczenia nigdy nie potrzebowałem takiej funkcji.
Corey Sunwold
+1 @Corey Sunwold: Dzięki za tę precyzję. Na razie znam tylko podobieństwa między C # i JAVĄ, wiedząc, że C # „odziedziczył” po JAVA w swoich koncepcjach. To powiedziawszy, zachęca mnie do dowiedzenia się więcej o JAVA, ponieważ wiem, że jest ona dobrze rozpowszechniona na całym świecie.
Will Marcouiller

Odpowiedzi:

62

Niestety nie możesz tego zrobić w C #.

Słowa constkluczowego można używać tylko w przypadku zmiennych i pól lokalnych.

Słowa readonlykluczowego można używać tylko w polach.

UWAGA: Język Java obsługuje również określanie końcowych parametrów metody. Ta funkcja nie istnieje w C #.

z http://www.25hoursaday.com/CsharpVsJava.html

EDYCJA (2019/08/13): Wrzucam to dla widoczności, ponieważ jest to akceptowane i najwyższe na liście. Jest to teraz możliwe dzięki inparametrom. Zobacz odpowiedź poniżej, aby uzyskać szczegółowe informacje.

Corey Sunwold
źródło
1
"Niestety"? Czym to się różni od obecnych możliwości?
John Saunders
31
@John Saunders Niefortunny, ponieważ Rosarch szuka funkcji, która nie istnieje.
Corey Sunwold
7
@John: Nie jest stała w metodzie. To jest problem.
Noon Silk
6
Jest to jedyna stała poza zakresem tej metody. Ignorując, czy został przekazany przez odwołanie, czy przez wartość, Rosarch nie chce, aby parametr zmieniał się w zakresie metody. To jest kluczowa różnica.
Corey Sunwold
5
@silky: czytając jego post, nie o to prosi. Prosi, aby metoda nie zmieniała rzeczywistych parametrów.
John Saunders
32

Jest to teraz możliwe w C # wersji 7.2:

Możesz użyć insłowa kluczowego w sygnaturze metody. Dokumentacja MSDN .

Słowo inkluczowe należy dodać przed określeniem argumentu metody.

Przykład, prawidłowa metoda w C # 7.2:

public long Add(in long x, in long y)
{
    return x + y;
}

Chociaż nie są dozwolone:

public long Add(in long x, in long y)
{
    x = 10; // It is not allowed to modify an in-argument.
    return x + y;
}

Poniższy błąd zostanie wyświetlony podczas próby zmodyfikowania xlub ypo oznaczeniu in:

Nie można przypisać do zmiennej „w długim”, ponieważ jest to zmienna tylko do odczytu

Oznaczanie argumentu inśrodkami:

Ta metoda nie modyfikuje wartości argumentu używanego jako ten parametr.

Maks
źródło
4
Oczywiście nie jest to przydatne w przypadku typów referencyjnych. Używanie insłowa kluczowego dla parametrów o tych typach zapobiega tylko przypisywaniu do nich. Nie zapobiega modyfikowaniu jego dostępnych pól lub właściwości! Czy mam rację?
ABS
5
Zwróć uwagę , że działa dobrze tylko ze strukturami / wartościami, a NIE z klasami, gdzie możesz zmodyfikować instancję przez ref, więc jest to znacznie gorsze. I nie dostajesz żadnych ostrzeżeń. Używaj ostrożnie. blog.dunnhq.com/index.php/2017/12/15/…
jeromej
8

Oto krótka i słodka odpowiedź, która prawdopodobnie otrzyma wiele głosów negatywnych. Nie przeczytałem wszystkich postów i komentarzy, więc proszę mi wybaczyć, jeśli zostało to wcześniej zasugerowane.

Dlaczego nie wziąć parametrów i nie przekazać ich do obiektu, który ujawnia je jako niezmienne, a następnie użyć tego obiektu w swojej metodzie?

Zdaję sobie sprawę, że jest to prawdopodobnie bardzo oczywiste obejście, które zostało już rozważone, a OP próbuje tego uniknąć, zadając to pytanie, ale czułem, że powinno być tutaj ...

Powodzenia :-)

Bennett Dill
źródło
1
Ponieważ członkowie nadal mogą być modyfikowani. Tego kodu C ++ pokazuje, że: int* p; *p = 0;. Spowoduje to skompilowanie i uruchomienie aż do błędu segmentacji.
Cole Johnson
Głosowałem w dół, ponieważ to nie jest rozwiązanie problemu. Możesz także zapisać argumenty w nagłówku funkcji i porównać je na końcu i zgłosić wyjątek w przypadku zmiany. Zachowam to w tylnej kieszeni, jeśli będzie kiedykolwiek konkurs na najgorsze rozwiązanie :)
Rick O'Shea
8

Odpowiedź: C # nie ma funkcji const, takiej jak C ++.

Zgadzam się z Bennettem Dillem.

Słowo kluczowe const jest bardzo przydatne. W tym przykładzie użyłeś int i ludzie nie rozumieją, o co ci chodzi. Ale dlaczego, jeśli parametr jest dużym i złożonym obiektem użytkownika, którego nie można zmienić w tej funkcji? To jest użycie słowa kluczowego const: parametr nie może się zmienić w tej metodzie, ponieważ [z jakiegokolwiek powodu] to nie ma znaczenia dla tej metody. Słowo kluczowe const jest bardzo potężne i bardzo mi go brakuje w C #.

Michel Vaz Ramos
źródło
7

Zacznę od intporcji. intjest typem wartości, aw .Net oznacza to, że naprawdę masz do czynienia z kopią. To naprawdę dziwne ograniczenie projektowe, aby powiedzieć metodzie „Możesz mieć kopię tej wartości. To twoja kopia, nie moja; nigdy więcej jej nie zobaczę. Ale nie możesz zmienić kopii”. W wywołaniu metody jest niejawne, że kopiowanie tej wartości jest w porządku, w przeciwnym razie nie moglibyśmy bezpiecznie wywołać metody. Jeśli metoda wymaga oryginału, pozostaw to osobie wdrażającej, aby wykonała kopię w celu jej zapisania. Albo nadaj metodzie wartość, albo nie nadaj metodzie wartości. Nie idź między tymi prymitywnymi pragnieniami.

Przejdźmy do typów referencyjnych. Teraz robi się trochę zagmatwane. Czy masz na myśli stałe odniesienie, w którym nie można zmienić samego odniesienia, czy też całkowicie zablokowany, niezmienny obiekt? W pierwszym przypadku odwołania w .Net są domyślnie przekazywane według wartości. Oznacza to, że otrzymujesz kopię referencji. Mamy więc zasadniczo taką samą sytuację, jak w przypadku typów wartości. Jeśli osoba wdrażająca będzie potrzebować oryginalnego odniesienia, może je zachować samodzielnie.

To po prostu pozostawia nam stały (zablokowany / niezmienny) obiekt. Może się to wydawać w porządku z perspektywy czasu wykonywania, ale jak kompilator ma to wymusić? Ponieważ wszystkie właściwości i metody mogą mieć skutki uboczne, zasadniczo byłbyś ograniczony do dostępu do pola tylko do odczytu. Taki obiekt raczej nie będzie interesujący.

Joel Coehoorn
źródło
2
Głosowałem na ciebie za niezrozumienie pytania; nie chodzi o zmianę wartości PO wywołaniu, ale zmianę jej W RAMACH.
Noon Silk
2
@silky - nie zrozumiałem źle pytania. Mówię też o funkcji. Mówię, że wysyłanie do funkcji jest dziwnym ograniczeniem, ponieważ tak naprawdę niczego nie zatrzymuje. Jeśli ktoś zapomni o zmianie pierwotnego parametru, równie prawdopodobne jest, że zapomni o zmienionej kopii.
Joel Coehoorn
1
Nie zgadzam się. Przedstawienie jedynie komentarza wyjaśniającego głos przeciw.
Noon Silk
DateTime ma dostęp tylko do pola tylko do odczytu, ale nadal jest interesujący. Ma wiele metod, które zwracają nowe instancje. Wiele języków funkcjonalnych unika metod z efektami ubocznymi i preferuje typy niezmienne, co moim zdaniem ułatwia śledzenie kodu.
Devin Garner,
DateTime jest również nadal typem wartości, a nie typem referencyjnym.
Joel Coehoorn
5

Utwórz interfejs dla swojej klasy, który ma tylko metody dostępu do właściwości tylko do odczytu. Następnie ustaw parametr na tym interfejsie, a nie na samej klasie. Przykład:

public interface IExample
{
    int ReadonlyValue { get; }
}

public class Example : IExample
{
    public int Value { get; set; }
    public int ReadonlyValue { get { return this.Value; } }
}


public void Foo(IExample example)
{
    // Now only has access to the get accessors for the properties
}

W przypadku struktur utwórz ogólne opakowanie const.

public struct Const<T>
{
    public T Value { get; private set; }

    public Const(T value)
    {
        this.Value = value;
    }
}

public Foo(Const<float> X, Const<float> Y, Const<float> Z)
{
// Can only read these values
}

Warto jednak zauważyć, że to dziwne, że chcesz robić to, o co prosisz w odniesieniu do struktur, jako autor metody, powinieneś się spodziewać, że wiesz, co się dzieje w tej metodzie. Nie wpłynie to na wartości przekazywane w celu zmodyfikowania ich w ramach metody, więc Twoim jedynym zmartwieniem jest upewnienie się, że zachowujesz się w metodzie, którą piszesz. Przychodzi moment, w którym czujność i czysty kod są kluczem do egzekwowania const i innych tego typu reguł.

Steve Lillis
źródło
To jest całkiem sprytne. Chcę również zauważyć, że możesz dziedziczyć wiele interfejsów w tym samym czasie - ale możesz dziedziczyć tylko jedną klasę.
Natalie Adams
Jest to pomocne ... i w ten sam sposób zmniejsza irytację wynikającą z braku go w języku C #. Dzięki
Jimmyt1988
3

Jeśli często masz takie kłopoty, powinieneś rozważyć „węgierskie aplikacje”. Dobry, w przeciwieństwie do złego . Chociaż zwykle nie próbuje to wyrazić stałości parametru metody (jest to po prostu zbyt niezwykłe), z pewnością nic nie powstrzymuje cię przed dodaniem dodatkowego „c” przed nazwą identyfikatora.

Wszystkim, którzy chcą teraz wcisnąć przycisk `` przeciw '', przeczytajcie opinie tych luminarzy na ten temat:

Hans Passant
źródło
Pamiętaj, że nie potrzebujesz konwencji nazewnictwa, aby to wymusić; zawsze możesz to zrobić za pomocą narzędzia analitycznego po fakcie (nie świetnie, ale mimo wszystko). Wierzę, że Gendarme ( mono-project.com/Gendarme ) ma na to pewną regułę i prawdopodobnie StyleCop / FxCop też.
Noon Silk
Najgorsza (nieudana) próba rozwiązania. Więc teraz twój parametr jest nie tylko zapisywalny, ale wystawia cię na porażkę, okłamując cię, że się nie zmienił.
Rick O'Shea
Dwóch obolałych użytkowników SO do tej pory mogło być gorzej.
Hans Passant
2

Wiem, że to może być trochę za późno. Ale dla osób, które wciąż szukają innych sposobów, może istnieć inny sposób obejścia tego ograniczenia standardu C #. Moglibyśmy napisać klasę opakowującą ReadOnly <T>, gdzie T: struct. Z niejawną konwersją do typu podstawowego T. Ale tylko jawna konwersja na klasę wrapper <T>. Który wymusi błędy kompilatora, jeśli deweloper spróbuje niejawnie ustawić wartość typu ReadOnly <T>. Jak pokażę poniżej dwa możliwe zastosowania.

USAGE 1 wymagało zmiany definicji rozmówcy. To użycie będzie używane tylko do testowania poprawności kodu funkcji „TestCalled”. Na poziomie wydania / kompilacji nie powinieneś go używać. Ponieważ operacje matematyczne na dużą skalę mogą nadmiernie wpływać na konwersje i spowalniać kod. Nie użyłbym tego, ale zamieściłem go tylko w celach demonstracyjnych.

ZASTOSOWANIE 2, które sugerowałbym, ma debugowanie vs wydanie pokazane w funkcji TestCalled2. Również nie byłoby konwersji w funkcji TestCaller przy zastosowaniu tego podejścia, ale wymaga to trochę więcej kodowania definicji TestCaller2 przy użyciu warunkowania kompilatora. Możesz zauważyć błędy kompilatora w konfiguracji debugowania, podczas gdy w konfiguracji wydania cały kod w funkcji TestCalled2 zostanie pomyślnie skompilowany.

using System;
using System.Collections.Generic;

public class ReadOnly<VT>
  where VT : struct
{
  private VT value;
  public ReadOnly(VT value)
  {
    this.value = value;
  }
  public static implicit operator VT(ReadOnly<VT> rvalue)
  {
    return rvalue.value;
  }
  public static explicit operator ReadOnly<VT>(VT rvalue)
  {
    return new ReadOnly<VT>(rvalue);
  }
}

public static class TestFunctionArguments
{
  static void TestCall()
  {
    long a = 0;

    // CALL USAGE 1.
    // explicite cast must exist in call to this function
    // and clearly states it will be readonly inside TestCalled function.
    TestCalled(a);                  // invalid call, we must explicit cast to ReadOnly<T>
    TestCalled((ReadOnly<long>)a);  // explicit cast to ReadOnly<T>

    // CALL USAGE 2.
    // Debug vs Release call has no difference - no compiler errors
    TestCalled2(a);

  }

  // ARG USAGE 1.
  static void TestCalled(ReadOnly<long> a)
  {
    // invalid operations, compiler errors
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }


  // ARG USAGE 2.
#if DEBUG
  static void TestCalled2(long a2_writable)
  {
    ReadOnly<long> a = new ReadOnly<long>(a2_writable);
#else
  static void TestCalled2(long a)
  {
#endif
    // invalid operations
    // compiler will have errors in debug configuration
    // compiler will compile in release
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    // compiler will compile in both, debug and release configurations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }

}
Słoneczny
źródło
Byłoby lepiej, gdyby ReadOnly był strukturą, a VT mógłby być zarówno strukturą, jak i klasą, w ten sposób, jeśli VT jest strukturą, wartość zostanie przekazana przez wartość, a jeśli jest to klasa, wartość zostanie przekazana przez referencję i jeśli chcesz, aby wyglądała następująco java ostateczna operator ReadOnly powinien być niejawny.
Tomer Wolberg
0

Jeśli struct zostanie przekazana do metody, chyba że zostanie przekazana przez ref, nie zostanie zmieniona przez metodę, do której została przekazana. Więc w tym sensie tak.

Czy możesz utworzyć parametr, którego wartości nie można przypisać w ramach metody lub którego właściwości nie można ustawić w ramach metody? Nie. Nie można zapobiec przypisywaniu wartości w metodzie, ale można zapobiec ustawianiu jej właściwości, tworząc niezmienny typ.

Nie chodzi o to, czy parametr lub jego właściwości można przypisać w ramach metody. Pytanie brzmi, co to będzie, gdy metoda zostanie zamknięta.

Jedynym przypadkiem, w którym jakiekolwiek zewnętrzne dane zostaną zmienione, jest przekazanie klasy i zmiana jednej z jej właściwości lub przekazanie wartości za pomocą słowa kluczowego ref. Sytuacja, którą nakreśliłeś, też nie.

David Morton
źródło
Dałbym +1 oprócz komentarza o niezmienności ... posiadanie niezmiennego typu nie przyniosłoby tego, czego szuka.
Adam Robinson
Pewnie, że domyślnie. Niezmienny typ, nieprzekazywany przez odwołanie, zapewni, że wartość poza metodą nie ulegnie zmianie.
David Morton,
1
To prawda, ale jego przykład dotyczy zmiany wartości wewnątrz metody. W jego przykładowym kodzie x to Int32, który jest niezmienny, ale nadal może pisać x++. Oznacza to, że próbuje zapobiec ponownemu przypisaniu parametru, co jest ortogonalne do zmienności wartości parametru.
itowlson
1
@silky Trochę dziwnie zignorować trzy odpowiedzi, które źle rozumiały to samo pytanie. Być może to nie wina czytelnika, że ​​pytanie zostało źle zrozumiane. Wiesz, kiedy mężczyzna rozwodzi się z trzecią żoną, zazwyczaj nie jest to wina żon.
David Morton
2
@David: Taki jest system głosowania. Aby uporządkować według trafności. Nie rozumiem, dlaczego powinno cię to obchodzić, żebyś został poprawiony.
Noon Silk