パラメータを一定にすることはできますか?


83

Javaと同等のC#を探していfinalます。それは存在しますか?

C#には次のようなものがありますか?

public Foo(final int bar);

上記の例でbarは、は読み取り専用変数であり、によって変更することはできませんFoo()。C#でこれを行う方法はありますか?

例えば、多分私は働くことになり、長い方法持っているxyzいくつかのオブジェクトの座標(int型)を。関数がこれらの値を変更してデータが破損しないことを絶対に確認したいと思います。したがって、私はそれらを読み取り専用と宣言したいと思います。

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

stackoverflow.com/questions/2125591/…の複製(そしてEric Lippertによる素晴らしい回答があります)
casperOne 2010

12
私はその正確な重複を考えていません。その質問は、参照渡しと値渡しの違いについてです。Rosarchは、彼が伝えようとしていたポイントに悪いサンプルコードを使用した可能性があると思います。
Corey Sunwold 2010

@Rosarch:「最終」という言葉から私が理解していることは、それが何であれ、オブジェクトに対してこれ以上アクションを実行できないということです。クラスに適用される「最終」は、C#での「封印」と同等であることを理解しています。しかし、とにかくこのキーワード「final」の利点または実際の使用法は何ですか?いつかJAVAソースコードでほとんどどこでも見たことがあります。
マルコイラーは2010

3
@Will Marcouiller最後のキーワードでは、変更できないオブジェクトではありません。オブジェクトの内部を変更するメソッドを使用できるため、変更できないオブジェクトへの参照です。与えられた例のように値渡しの状況の場合、値が変化するため、x ++が有効な操作であることが妨げられます。利点は、コンパイル時のサニティチェックを取得して、値が異なるように設定されていないことを確認できることです。しかし、私の経験では、そのような機能は必要ありませんでした。
Corey Sunwold 2010

+1 @Corey Sunwold:この精度に感謝します。今のところ、私はC#とJAVAの類似点しか知りません。その概念では、C#がJAVAから「継承」されていることを知っています。そうは言っても、JAVAは世界中に広く普及していることを知っているので、JAVAについてもっと学ぶことを奨励しています。
マルコイラーは2010

回答:


60

残念ながら、C#ではこれを行うことはできません。

constキーワードは、ローカル変数とフィールドのために使用することができます。

readonlyキーワードは、フィールドのみで使用することができます。

注:Java言語は、メソッドへの最終パラメーターを持つこともサポートしています。この機能はC#には存在しません。

http://www.25hoursaday.com/CsharpVsJava.htmlから

編集(2019/08/13):これは受け入れられており、リストの上位にあるため、可視性のためにこれを投入しています。これで、inパラメータを使用して可能になりました。詳細については、この下の回答を参照してください。


1
「残念ながら」?それは現在の機能とどのように異なりますか?
ジョンサンダース

31
@John Saunders残念ながら、Rosarchは存在しない機能を探しています。
Corey Sunwold 2010

7
@John:メソッド内では一定ではありません。それが問題です。
正午シルク

6
このメソッドの範囲外の唯一の定数。Rosarchは、参照によって渡されたか値によって渡されたかを無視して、メソッドのスコープ内でパラメーターが変更されることを望んでいません。それが重要な違いです。
Corey Sunwold 2010

5
@silky:彼の投稿を読んで、それは彼が求めているものではありません。彼は、メソッドが実際のパラメーターを変更しないようにすることを求めています。
ジョンサンダース

30

これは、C#バージョン7.2で可能になりました。

inメソッドシグネチャでキーワードを使用できます。MSDNドキュメント

inメソッドの引数を指定する前に、キーワードを追加する必要があります。

例、C#7.2の有効なメソッド:

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

以下は許可されていませんが:

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

いずれかを変更しようとすると、xまたは次のyマークが付いているため、次のエラーが表示されますin

読み取り専用変数であるため、変数 'をlong'に割り当てることはできません

引数にin手段を付ける:

このメソッドは、このパラメーターとして使用される引数の値を変更しません。


3
もちろん、参照型には役立ちません。使用してin、これらの型のパラメータのためのキーワードを、それらだけに割り当てる防ぎます。アクセス可能なフィールドまたはプロパティの変更を妨げないでください!私は正しいですか?
ABS

5
構造体/値でのみうまく機能し、クラスでは機能しないことに注意してください。クラスでは、refによってインスタンスを変更できるため、さらに悪化します。そして、あなたは何の警告も受けません。注意して使用してください。blog.dunnhq.com/index.php/2017/12/15/...
jeromej

8

これは、おそらく多くの反対票を獲得するであろう短くて甘い答えです。私はすべての投稿とコメントを読んだわけではないので、これが以前に提案された場合はご容赦ください。

パラメータを取得して、それらを不変として公開するオブジェクトに渡してから、そのオブジェクトをメソッドで使用してみませんか?

これはおそらくすでに検討されている非常に明白な回避策であり、OPはこの質問をすることでこれを回避しようとしていることを理解していますが、それでもここにあるべきだと感じました...

幸運を :-)


1
メンバーはまだ変更できるからです。このc ++コードは、次のことを示していますint* p; *p = 0;。これはコンパイルされ、セグメンテーション違反が発生するまで実行されます。
コールジョンソン

それは問題の解決策ではないので、私は反対票を投じました。関数の先頭に引数を保存して最後に比較し、変更時に例外をスローすることもできます。最悪の解決策のコンテストがあった場合は、それをバックポケットに入れておきます:)
Rick O'Shea 2018

8

答え:C#にはC ++のようなconst機能がありません。

私はベネットディルに同意します。

constキーワードは非常に便利です。この例では、intを使用しましたが、人々はあなたの主張を理解していません。しかし、パラメーターがユーザーの巨大で複雑なオブジェクトであり、その関数内で変更できない場合はどうでしょうか。これがconstキーワードの使用です。[ここでの理由が何であれ]それはそのメソッドにとって重要ではないため、パラメーターはそのメソッド内で変更できません。Constキーワードは非常に強力で、C#では本当に見逃しています。


7

そのint部分から始めましょう。 intは値型であり、.Netでは、実際にコピーを処理していることを意味します。「この値のコピーを持つことができます。これは私のものではなく、あなたのコピーです。二度と表示されません。ただし、コピーを変更することはできません。」というのは、非常に奇妙な設計上の制約です。この値をコピーしても問題がないことはメソッド呼び出しに暗黙的に含まれています。そうしないと、メソッドを安全に呼び出すことができませんでした。メソッドにオリジナルが必要な場合は、実装者に任せてコピーを作成し、保存してください。メソッドに値を指定するか、メソッドに値を指定しないでください。間にすべてのウィッシュウォッシュを行かないでください。

参照型に移りましょう。今では少し混乱します。参照自体を変更できない定数参照、または完全にロックされた変更不可能なオブジェクトを意味しますか?前者の場合、デフォルトで.Netの参照は値で渡されます。つまり、参照のコピーを取得します。したがって、値型の場合と本質的に同じ状況になります。実装者が元の参照を必要とする場合は、それを自分で保持できます。

これにより、一定の(ロックされた/不変の)オブジェクトが残ります。これはランタイムの観点からは問題ないように見えるかもしれませんが、コンパイラーはどのようにそれを強制しますか?プロパティとメソッドはすべて副作用がある可能性があるため、基本的に読み取り専用のフィールドアクセスに制限されます。そのようなオブジェクトはあまり面白くないでしょう。


1
私はその質問を誤解したことであなたに反対票を投じました。呼び出し後に値を変更するのではなく、呼び出し内で値を変更します。
正午シルク

2
@ silky-私は質問を誤解していませんでした。関数内でも話します。関数に送信するのは奇妙な制限だと言っています。なぜなら、それは実際には何も停止しないからです。誰かが元のパラメータの変更を忘れた場合、変更されたコピーを忘れる可能性があります。
Joel Coehoorn 2010

1
同意しません。反対票を説明するコメントを提供するだけです。
正午シルク

DateTimeには読み取り専用のフィールドアクセスのみがありますが、それでも興味深いものです。新しいインスタンスを返す多くのメソッドがあります。多くの関数型言語は、副作用のあるメソッドを避け、不変の型を好みます。私の意見では、これによりコードがわかりやすくなります。
Devin Garner 2013

DateTimeも値型であり、参照型ではありません。
Joel Coehoorn 2015

5

読み取り専用のプロパティアクセサーのみを持つクラスのインターフェイスを作成します。次に、パラメータをクラス自体ではなく、そのインターフェイスのものにします。例:

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
}

構造体の場合は、汎用の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
}

ただし、構造体に関して求めていることを実行したいというのは奇妙なことです。メソッドの作成者として、そのメソッドで何が起こっているかを知っていることを期待する必要があります。メソッド内で値を変更するために渡される値には影響しないため、唯一の懸念事項は、作成しているメソッドで自分自身を動作させることです。constや他のそのようなルールを強制するよりも、警戒とクリーンなコードが鍵となるポイントがあります。


それは実際にはかなり賢いです。また、同時に複数のインターフェースを継承できることにも注意したいのですが、継承できるのは1つのクラスだけです。
ナタリーアダムス

これは役に立ちます...そして同じようにc#から欠落している煩わしさを和らげます。ありがとう
Jimmyt1988 2015年

3

このような問題が頻繁に発生する場合は、「アプリハンガリー語」を検討する必要があります。悪い種類ではなく、良い種類。これは通常、メソッドパラメータの一定性を表現しようとはしませんが(これはあまりにも珍しいことです)、識別子名の前に余分な「c」を付けることを妨げるものは確かにありません。

今すぐ反対票を押すことを切望しているすべての人に、このトピックに関するこれらの著名人の意見を読んでください:


これを強制するために命名規則は必要ないことに注意してください。事後に分析ツールを使用していつでもそれを行うことができます(素晴らしいとは言えませんが、それでも)。Gendarme(mono-project.com/Gendarme)にはルールがあり、おそらくStyleCop / FxCopにもルールがあると思います。
正午シルク

ソリューションでの最悪の試み(失敗)は簡単です。これで、パラメーターが書き込み可能になるだけでなく、パラメーターが変更されていないことについて嘘をつくことで、失敗の準備が整います。
Rick O'Shea 2018

これまでのところ、2人の痛むSOユーザーはもっとひどかったかもしれません。
ハンスパッサント2018

2

私はこれが少し遅れるかもしれないことを知っています。しかし、まだ他の方法を探している人にとっては、C#標準のこの制限を回避する別の方法があるかもしれません。ラッパークラスReadOnly <T>を書くことができます。ここでT:structです。基本型Tへの暗黙的な変換を使用します。ただし、wrapper <T>クラスへの明示的な変換のみです。開発者がReadOnly <T>タイプの値に暗黙的に設定しようとすると、コンパイラエラーが発生します。以下に2つの可能な使用法を示します。

USAGE 1では、呼び出し元の定義を変更する必要がありました。この使用法は、「TestCalled」関数コードの正確さをテストする場合にのみ使用されます。リリースレベル/ビルドでは、使用しないでください。大規模な数学演算では、変換が過剰になり、コードが遅くなる可能性があるためです。私はそれを使用しませんが、デモンストレーションの目的でのみ投稿しました。

私が提案するUSAGE2では、TestCalled2関数でデバッグとリリースの使用法が示されています。また、このアプローチを使用する場合、TestCaller関数で変換は行われませんが、コンパイラー条件付けを使用してTestCaller2定義をコーディングする必要があります。デバッグ構成でコンパイラエラーに気付くことがありますが、リリース構成では、TestCalled2関数のすべてのコードが正常にコンパイルされます。

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

}

ReadOnlyが構造体であり、VTが構造体とクラスの両方である場合は、より良いでしょう。そうすれば、VTが構造体の場合、値は値によって渡され、クラスの場合、値は参照によって渡されます。 javaの最後の演算子ReadOnlyは暗黙的である必要があります。
Tomer

0

構造体がメソッドに渡された場合、refによって渡されない限り、渡されたメソッドによって変更されることはありません。そういう意味ではそうです

メソッド内で値を割り当てることができない、またはメソッド内でプロパティを設定できないパラメーターを作成できますか?いいえ。メソッド内で値が割り当てられるのを防ぐことはできませんが、不変の型を作成することで、プロパティが設定されるのを防ぐことができます。

問題は、パラメータまたはそのプロパティをメソッド内で割り当てることができるかどうかではありません。問題は、メソッドが終了したときにどうなるかです。

外部データが変更されるのは、クラスを渡してそのプロパティの1つを変更する場合、またはrefキーワードを使用して値を渡す場合のみです。あなたが概説した状況はどちらもしません。


不変性についてのコメントを除いて、私は+1します...不変の型を持っていると、彼が探していることを達成できません。
アダムロビンソン

確かに、デフォルトではそうなるでしょう。参照によって渡されない不変の型は、メソッドの外部の値が変更されないことを保証します。
デビッド・モートン

1
本当ですが、彼の例はメソッドの値変更することです。彼のサンプルコードでは、xはInt32であり、不変ですが、それでも書き込みは許可されていますx++。つまり、彼は、パラメーターの値の可変性に直交するパラメーターの再割り当てを防止しようとしています。
itowlson 2010

1
@silkyちょっと、同じ質問を誤解した3つの答えに反対票を投じるのは奇妙です。おそらく、質問が誤解されたのは読者のせいではありません。男性が3人目の妻と離婚するとき、それは通常、妻のせいではありません。
デビッドモートン

2
@David:それが投票システムの目的です。関連性で注文する。なぜあなたが訂正されることに関係するのか分かりません。
正午シルク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.