Können Parameter konstant sein?

83

Ich suche das C # -Äquivalent von Java final. Existiert es?

Hat C # Folgendes:

public Foo(final int bar);

Im obigen Beispiel barhandelt es sich um eine schreibgeschützte Variable, die nicht von geändert werden kann Foo(). Gibt es eine Möglichkeit, dies in C # zu tun?

Zum Beispiel, vielleicht habe ich eine lange Methode , die mit arbeiten werden x, yund zKoordinaten eines Objekts (Ints). Ich möchte absolut sicher sein, dass die Funktion diese Werte in keiner Weise ändert und dadurch die Daten beschädigt. Daher möchte ich sie schreibgeschützt erklären.

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
quelle
Duplikat von stackoverflow.com/questions/2125591/… (und hat eine großartige Antwort von Eric Lippert, übrigens)
casperOne
12
Ich glaube nicht, dass es genau dupliziert wird. Bei dieser Frage geht es um den Unterschied zwischen Referenzübergabe und Wertübergabe. Ich denke, Rosarch hat möglicherweise schlechten Beispielcode für den Punkt verwendet, den er zu vermitteln versuchte.
Corey Sunwold
@Rosarch: Was ich unter dem Wort "final" verstehe, ist, dass Sie mit dem Objekt mehr Aktionen ausführen können, was auch immer das sein mag. Ich verstehe, dass "final", das auf eine Klasse angewendet wird, die Äquivalenz von "versiegelt" in C # ist. Aber was ist der Nutzen oder die tatsächliche Verwendung dieses Schlüsselworts "final" überhaupt? Ich habe es eines Tages fast an jedem Ort in einem JAVA-Quellcode gesehen.
Will Marcouiller
3
@ Will Marcouiller Mit dem letzten Schlüsselwort ist es nicht das Objekt, das sich nicht ändern kann, da Sie eine Methode verwenden können, die sich in den Interna des Objekts ändert, sondern der Verweis auf das Objekt, das sich nicht ändern kann. Im Fall einer Situation, in der Werte übergeben werden, wie in dem angegebenen Beispiel, würde dies verhindern, dass x ++ eine gültige Operation ist, da sich der Wert ändern würde. Der Vorteil wäre, dass Sie eine Überprüfung der Kompilierungszeit erhalten, um sicherzustellen, dass der Wert durch nichts anders festgelegt wird. Nach meiner Erfahrung habe ich jedoch noch nie eine solche Funktion benötigt.
Corey Sunwold
+1 @Corey Sunwold: Danke für diese Präzision. Derzeit kenne ich nur Ähnlichkeiten zwischen C # und JAVA, da ich weiß, dass C # in seinen Konzepten von JAVA "geerbt" wurde. Dies ermutigt mich, mehr über JAVA zu erfahren, da ich weiß, dass es weltweit gut verbreitet ist.
Will Marcouiller

Antworten:

60

Leider können Sie dies in C # nicht tun.

Das const Schlüsselwort kann nur für lokale Variablen und Felder verwendet werden.

Das readonlySchlüsselwort kann nur für Felder verwendet werden.

HINWEIS: Die Java-Sprache unterstützt auch die endgültigen Parameter für eine Methode. Diese Funktionalität ist in C # nicht vorhanden.

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

EDIT (2019/08/13): Ich werfe dies zur besseren Sichtbarkeit ein, da dies akzeptiert wird und auf der Liste am höchsten ist. Mit inParametern ist das jetzt irgendwie möglich . Siehe die Antwort unter dieser für Details.

Corey Sunwold
quelle
1
"Unglücklicherweise"? Wie würde sich das von der aktuellen Fähigkeit unterscheiden?
John Saunders
31
@ John Saunders Leider, weil Rosarch nach einer Funktion sucht, die es nicht gibt.
Corey Sunwold
7
@ John: Es ist nicht konstant innerhalb der Methode. Das ist das Problem.
Mittag Seide
6
Es ist die einzige Konstante außerhalb des Anwendungsbereichs dieser Methode. Rosarch ignoriert, ob es als Referenz oder als Wert übergeben wurde, und möchte nicht, dass sich der Parameter im Rahmen der Methode ändert. Das ist der Hauptunterschied.
Corey Sunwold
5
@silky: seinen Beitrag lesen, das ist nicht das, wonach er fragt. Er möchte sicherstellen, dass die Methode die tatsächlichen Parameter nicht ändert.
John Saunders
29

Dies ist jetzt in C # Version 7.2 möglich:

Sie können das inSchlüsselwort in der Methodensignatur verwenden. MSDN-Dokumentation .

Das in Schlüsselwort sollte hinzugefügt werden, bevor das Argument einer Methode angegeben wird.

Beispiel eine gültige Methode in C # 7.2:

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

Während folgendes nicht erlaubt ist:

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

Folgende Fehler werden angezeigt, wenn entweder zu ändern versuchen , xoder yweil sie markiert sind , mit in:

Kann der Variablen 'in long' nicht zugewiesen werden, da es sich um eine schreibgeschützte Variable handelt

Ein Argument mit inMitteln markieren :

Diese Methode ändert den Wert des als dieser Parameter verwendeten Arguments nicht.

Max
quelle
3
Natürlich ist es für Referenztypen nicht nützlich. Wenn Sie inSchlüsselwörter für Parameter mit diesen Typen verwenden, verhindern Sie nur die Zuweisung zu diesen. Verhindern Sie nicht, die zugänglichen Felder oder Eigenschaften zu ändern! Habe ich recht?
ABS
3
Bitte beachten Sie, dass es nur mit struct / values ​​und NICHT mit Klassen gut funktioniert, wo Sie die Instanz ändern können, die per ref ist, so dass das viel schlimmer ist. Und Sie erhalten keinerlei Warnungen. Mit Vorsicht verwenden. blog.dunnhq.com/index.php/2017/12/15/…
jeromej
8

Hier ist eine kurze und süße Antwort, die wahrscheinlich viele negative Stimmen erhalten wird. Ich habe nicht alle Beiträge und Kommentare gelesen. Bitte verzeihen Sie mir, wenn dies zuvor vorgeschlagen wurde.

Nehmen Sie Ihre Parameter und übergeben Sie sie an ein Objekt, das sie als unveränderlich verfügbar macht, und verwenden Sie dieses Objekt dann in Ihrer Methode.

Mir ist klar, dass dies wahrscheinlich eine sehr offensichtliche Lösung ist, die bereits in Betracht gezogen wurde, und das OP versucht, dies zu vermeiden, indem es diese Frage stellt, aber ich war der Meinung, dass es trotzdem hier sein sollte ...

Viel Glück :-)

Bennett Dill
quelle
1
Weil Mitglieder noch geändert werden können. Dieser C ++ - Code zeigt Folgendes : int* p; *p = 0;. Das wird kompiliert und bis zum Segmentierungsfehler ausgeführt.
Cole Johnson
Ich habe abgestimmt, weil es keine Lösung für das Problem ist. Sie können die Argumente auch am Funktionskopf speichern und am Ende vergleichen und bei Änderungen eine Ausnahme auslösen. Ich werde das in meiner Gesäßtasche behalten, wenn es jemals einen Wettbewerb mit der schlechtesten Lösung gibt :)
Rick O'Shea
8

Die Antwort: C # hat nicht die const-Funktionalität wie C ++.

Ich stimme Bennett Dill zu.

Das Schlüsselwort const ist sehr nützlich. In diesem Beispiel haben Sie ein int verwendet, und die Leute verstehen Ihren Standpunkt nicht. Aber warum, wenn Ihr Parameter ein riesiges und komplexes Objekt des Benutzers ist, das innerhalb dieser Funktion nicht geändert werden kann? Das ist die Verwendung des Schlüsselworts const: parameter kann innerhalb dieser Methode nicht geändert werden, da [aus welchem ​​Grund auch immer] dies für diese Methode nicht wichtig ist. Das Const-Schlüsselwort ist sehr mächtig und ich vermisse es wirklich in C #.

Michel Vaz Ramos
quelle
7

Ich werde mit der intPortion beginnen. intist ein Werttyp, und in .Net bedeutet dies, dass es sich wirklich um eine Kopie handelt. Es ist eine wirklich seltsame Designbeschränkung, einer Methode mitzuteilen: "Sie können eine Kopie dieses Werts haben. Es ist Ihre Kopie, nicht meine. Ich werde sie nie wieder sehen. Aber Sie können die Kopie nicht ändern." Der Methodenaufruf impliziert, dass das Kopieren dieses Werts in Ordnung ist, da wir die Methode sonst nicht sicher aufrufen könnten. Wenn die Methode das Original benötigt, überlassen Sie es dem Implementierer, eine Kopie zum Speichern zu erstellen. Geben Sie der Methode entweder den Wert oder geben Sie der Methode nicht den Wert. Gehen Sie nicht zwischendurch auf die Wäsche.

Kommen wir zu den Referenztypen. Jetzt wird es etwas verwirrend. Meinen Sie eine konstante Referenz, bei der die Referenz selbst nicht geändert werden kann, oder ein vollständig verriegeltes, unveränderliches Objekt? In diesem Fall werden Verweise in .Net standardmäßig als Wert übergeben. Das heißt, Sie erhalten eine Kopie der Referenz. Wir haben also im Wesentlichen die gleiche Situation wie bei Werttypen. Wenn der Implementierer die ursprüngliche Referenz benötigt, kann er diese selbst behalten.

Das lässt uns nur mit einem konstanten (gesperrten / unveränderlichen) Objekt zurück. Dies mag aus Sicht der Laufzeit in Ordnung erscheinen, aber wie soll der Compiler dies durchsetzen? Da Eigenschaften und Methoden alle Nebenwirkungen haben können, sind Sie im Wesentlichen auf den schreibgeschützten Feldzugriff beschränkt. Ein solches Objekt dürfte nicht sehr interessant sein.

Joel Coehoorn
quelle
1
Ich habe Sie wegen Missverständnisses der Frage abgelehnt. Es geht nicht darum, den Wert NACH dem Aufruf zu ändern, sondern ihn INNERHALB zu ändern.
Mittag Seide
2
@silky - Ich habe die Frage nicht falsch verstanden. Ich spreche auch innerhalb der Funktion. Ich sage, es ist eine seltsame Einschränkung, an eine Funktion zu senden, weil es nichts wirklich aufhält. Wenn jemand vergisst, dass der ursprüngliche Parameter geändert wurde, vergisst er genauso wahrscheinlich eine geänderte Kopie.
Joel Coehoorn
1
Ich bin nicht einverstanden. Nur einen Kommentar zur Erklärung der Ablehnung abgeben.
Mittag Seide
DateTime hat nur schreibgeschützten Feldzugriff, ist aber immer noch interessant. Es gibt viele Methoden, die neue Instanzen zurückgeben. Viele funktionale Sprachen vermeiden Methoden mit Nebenwirkungen und bevorzugen unveränderliche Typen. Meiner Meinung nach erleichtert dies das Befolgen von Code.
Devin Garner
DateTime ist auch weiterhin ein Werttyp, kein Referenztyp.
Joel Coehoorn
5

Erstellen Sie eine Schnittstelle für Ihre Klasse, die nur schreibgeschützte Eigenschaftszugriffsmethoden enthält. Lassen Sie dann Ihren Parameter von dieser Schnittstelle und nicht von der Klasse selbst sein. Beispiel:

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
}

Erstellen Sie für Strukturen einen generischen const-Wrapper.

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
}

Es ist jedoch erwähnenswert, dass es seltsam ist, dass Sie das tun möchten, was Sie in Bezug auf Strukturen tun möchten. Als Verfasser der Methode sollten Sie erwarten, dass Sie wissen, was in dieser Methode vor sich geht. Die übergebenen Werte werden nicht beeinflusst, um sie innerhalb der Methode zu ändern. Sie müssen also nur sicherstellen, dass Sie sich in der von Ihnen geschriebenen Methode verhalten. Es kommt ein Punkt, an dem Wachsamkeit und sauberer Code der Schlüssel sind, um const und andere solche Regeln durchzusetzen.

Steve Lillis
quelle
Das ist eigentlich ziemlich schlau. Ich möchte auch darauf hinweisen, dass Sie mehrere Schnittstellen gleichzeitig erben können - aber Sie können nur eine Klasse erben.
Natalie Adams
Dies ist hilfreich ... und lindert auf die gleiche Weise den Ärger, der in c # fehlt. Danke
Jimmyt1988
3

Wenn Sie häufig auf solche Probleme stoßen, sollten Sie "Apps ungarisch" in Betracht ziehen. Die gute Art im Gegensatz zur schlechten Art . Während dies normalerweise nicht versucht, die Konstanz eines Methodenparameters auszudrücken (das ist einfach zu ungewöhnlich), hindert Sie nichts daran, ein zusätzliches "c" vor dem Bezeichnernamen anzuheften.

Lesen Sie bitte die Meinungen dieser Koryphäen zum Thema: Alle, die jetzt den Downvote-Button drücken möchten:

Hans Passant
quelle
Beachten Sie, dass Sie keine Namenskonvention benötigen, um dies durchzusetzen. Sie könnten es immer nachträglich mit einem Analysetool tun (nicht großartig, aber trotzdem). Ich glaube, Gendarme ( mono-project.com/Gendarme ) hat eine Regel dafür und wahrscheinlich auch StyleCop / FxCop.
Mittag Seide
Leicht der schlechteste (fehlgeschlagene) Lösungsversuch. Jetzt ist Ihr Parameter nicht nur beschreibbar, er bereitet Sie auch auf einen Fehler vor, indem er Sie anlügt, dass er sich nicht geändert hat.
Rick O'Shea
Zwei schmerzende SO-Benutzer hätten schlimmer sein können.
Hans Passant
2

Ich weiß, dass dies etwas spät sein könnte. Aber für Leute, die immer noch nach anderen Wegen suchen, könnte es einen anderen Weg geben, um diese Einschränkung des C # -Standards zu umgehen. Wir könnten die Wrapper-Klasse ReadOnly <T> schreiben, wobei T: struct. Mit impliziter Konvertierung in Basistyp T. Aber nur explizite Konvertierung in die Wrapper <T> -Klasse. Dies erzwingt Compilerfehler, wenn der Entwickler versucht, den Wert vom Typ ReadOnly <T> implizit auf den Wert zu setzen. Wie ich im Folgenden zwei Verwendungsmöglichkeiten demonstrieren werde.

Für die Verwendung von USAGE 1 musste die Anruferdefinition geändert werden. Diese Verwendung wird nur zum Testen der Richtigkeit Ihres Funktionscodes "TestCalled" verwendet. Während der Release-Version / Builds sollten Sie es nicht verwenden. Da im großen Maßstab mathematische Operationen bei Konvertierungen möglicherweise zu viel bewirken und Ihren Code verlangsamen. Ich würde es nicht benutzen, aber nur zu Demonstrationszwecken habe ich es gepostet.

USAGE 2, das ich vorschlagen würde, hat die Verwendung von Debug vs Release in der TestCalled2-Funktion demonstriert. Bei Verwendung dieses Ansatzes würde die TestCaller-Funktion ebenfalls nicht konvertiert, erfordert jedoch ein wenig mehr Codierung der TestCaller2-Definitionen mithilfe der Compilerkonditionierung. Sie können Compilerfehler in der Debug-Konfiguration feststellen, während bei der Release-Konfiguration der gesamte Code in der TestCalled2-Funktion erfolgreich kompiliert wird.

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;
  }

}
Solar
quelle
Es wäre besser, wenn ReadOnly struct wäre und VT sowohl struct als auch class sein könnte. Wenn VT struct ist, wird der Wert als Wert übergeben, und wenn es sich um eine Klasse handelt, wird der Wert als Referenz übergeben und wenn Sie möchten, dass er so ist Java's Final Der Operator ReadOnly sollte implizit sein.
Tomer Wolberg
0

Wenn struct an eine Methode übergeben wird, wird es von der Methode, an die es übergeben wird, nicht geändert, es sei denn, es wird von ref übergeben. In diesem Sinne ja.

Können Sie einen Parameter erstellen, dessen Wert innerhalb der Methode nicht zugewiesen werden kann oder dessen Eigenschaften innerhalb der Methode nicht festgelegt werden können? Nein. Sie können nicht verhindern, dass der Wert innerhalb der Methode zugewiesen wird, aber Sie können verhindern, dass seine Eigenschaften festgelegt werden, indem Sie einen unveränderlichen Typ erstellen.

Die Frage ist nicht, ob der Parameter oder seine Eigenschaften innerhalb der Methode zugewiesen werden können. Die Frage ist, was es sein wird, wenn die Methode beendet wird.

Externe Daten werden nur geändert, wenn Sie eine Klasse übergeben und eine ihrer Eigenschaften ändern oder wenn Sie einen Wert mit dem Schlüsselwort ref übergeben. Die Situation, die Sie skizziert haben, tut beides nicht.

David Morton
quelle
Ich würde +1 außer dem Kommentar über Unveränderlichkeit ... einen unveränderlichen Typ zu haben, würde nicht das erreichen, wonach er sucht.
Adam Robinson
Sicher würde es standardmäßig. Ein unveränderlicher Typ, der nicht als Referenz übergeben wird, stellt sicher, dass sich der Wert außerhalb der Methode nicht ändert.
David Morton
1
Stimmt, aber in seinem Beispiel geht es darum, den Wert innerhalb der Methode zu ändern . In seinem Beispielcode ist x ein Int32, der unveränderlich ist, aber er darf trotzdem schreiben x++. Das heißt, er versucht, eine Neuzuweisung des Parameters zu verhindern, die orthogonal zur Veränderbarkeit des Parameterwerts ist.
Itowlson
1
@silky Irgendwie seltsam, drei Antworten herunterzustimmen, die dieselbe Frage missverstanden haben. Vielleicht ist es nicht die Schuld des Lesers, dass die Frage missverstanden wurde. Wissen Sie, wenn sich ein Mann von seiner dritten Frau scheiden lässt, ist dies normalerweise nicht die Schuld der Frauen.
David Morton
2
@ David: Es ist der Zweck des Abstimmungssystems. Nach Relevanz bestellen. Ich verstehe nicht, warum es Sie betreffen sollte, korrigiert zu werden.
Mittag Seide