web-dev-qa-db-ja.com

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

Javaのfinalに相当するC#を探しています。存在しますか?

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

_public Foo(final int bar);
_

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

たとえば、あるオブジェクト(int)のxy、およびz座標で動作する長いメソッドがあるかもしれません。私は、関数がこれらの値を決して変更せず、それによってデータが破損しないことを絶対に確認したいと思います。したがって、それらを読み取り専用として宣言したいと思います。

_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.
}
_
78
Nick Heiner

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

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

readonly キーワードはフィールドでのみ使用できます。

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

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

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

59
Corey Sunwold

これは、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のいずれかを変更しようとすると、次のエラーが表示されます。

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

引数をinでマークすると、次のことを意味します。

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

22
Max

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

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

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

8
Joel Coehoorn

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

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

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

8

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

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

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

がんばろう :-)

7
Bennett Dill

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

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や他のそのようなルールを強制するよりも、警戒とクリーンなコードが重要になるポイントがあります。

5
Steve Lillis

このような問題が頻繁に発生する場合は、「ハンガリーのアプリ」を検討してください。 bad kind とは対照的な、良い種類。これは通常、メソッドパラメータの定数を表現しようとはしませんが(あまりにも珍しいことです)、識別子名の前に余分な「c」を追加することを妨げるものはありません。

今すぐ下投票ボタンを押したがるすべての人々に、トピックに関するこれらの著名人の意見を読んでください:

3
Hans Passant

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

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

私が提案するUSAGE 2では、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;
  }

}
2
SoLaR

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

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

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

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

0
David Morton