読み取り専用フィールドに不純なメソッドが呼び出されます


83

私が使用しているのVisual Studio 2010 + ReSharperのを、それが次のコードに警告を示しています。

if (rect.Contains(point))
{
    ...
}

rectreadonly Rectangleフィールドであり、Resharperは次の警告を表示します。

「値型の読み取り専用フィールドに対してImpureメソッドが呼び出されます。」

不純な方法とは何ですか?なぜこの警告が表示されるのですか?


回答:


96

まず、Jon、Michael、Jaredの答えは基本的に正しいですが、さらにいくつか追加したいことがあります。

「不純な」方法とはどういう意味ですか?

純粋なメソッドを特徴づける方が簡単です。「純粋」メソッドには、次の特徴があります。

  • その出力は完全にその入力によって決定されます。その出力は、時刻やハードディスクのビットなどの外部性に依存しません。その出力はその履歴に依存しません。指定された引数を使用してメソッドを2回呼び出すと、同じ結果が得られます。
  • 純粋な方法では、周囲の世界で観察可能な突然変異は発生しません。純粋メソッドは、効率を上げるためにプライベート状態を変更することを選択できますが、純粋メソッドは、たとえば、引数のフィールドを変更しません。

たとえば、Math.Cosは純粋な方法です。その出力はその入力のみに依存し、入力は呼び出しによって変更されません。

不純な方法は、純粋ではない方法です。

読み取り専用構造体を渡して不純なメソッドにすることの危険性にはどのようなものがありますか?

頭に浮かぶのは2つあります。1つ目は、Jon、Michael、Jaredが指摘したもので、これはResharperが警告しているものです。構造体でメソッドを呼び出すと、メソッドが変数を変更したい場合に備えて、レシーバーである変数への参照を常に渡します。

では、変数ではなく値に対してそのようなメソッドを呼び出すとどうなるでしょうか。その場合、一時変数を作成し、その値をコピーして、変数への参照を渡します。

読み取り専用変数は、コンストラクターの外部で変更できないため、値と見なされます。そのため、変数を別の変数にコピーしています。変数を変更する場合は、不純なメソッドがコピーを変更している可能性があります。

これは、読み取り専用構造体をレシーバーとして渡すことの危険性です。読み取り専用フィールドを含む構造体を渡す危険もあります。読み取り専用フィールドを含む構造体は一般的な方法ですが、基本的には、型システムに現金化する資金がないことを確認するためのチェックを記述しています。特定の変数の「読み取り専用性」は、ストレージの所有者によって決定されます。参照型のインスタンスはそれ自体のストレージを「所有」しますが、値型のインスタンスは所有しません。

struct S
{
  private readonly int x;
  public S(int x) { this.x = x; }
  public void Badness(ref S s)
  {
    Console.WriteLine(this.x);   
    s = new S(this.x + 1);
    // This should be the same, right?
    Console.WriteLine(this.x);   
  }
}

this.xxは読み取り専用フィールドでありBadness、コンストラクターではないため、これは変更されないと考えられます。だが...

S s = new S(1);
s.Badness(ref s);

...その虚偽を明確に示しています。同じ変数thiss参照し、その変数は読み取り専用ではありません!


十分に公平ですが、次のコードを検討してください: ToIntが不純なのstruct Id { private readonly int _id; public Id(int id) { _id = id; } public int ToInt() => _id; } はなぜですか?
boskicthebrain 2017

@boskicthebrain:あなたの質問は実際に「なぜResharperはこれを不純だと見なすのですか?」それがあなたの質問であるなら、R#に取り組んでいる誰かを見つけて彼らに尋ねてください!
Eric Lippert 2017

3
メソッドが無効で、以外は何もしない場合でも、Resharperはこの警告を表示しreturnます。それに基づいて、唯一の基準はメソッドが[Pure]属性を持っているかどうかだと思います。
bornfromanegg

「レシーバーである変数への参照を常に渡す」というこのステートメントは、私には少し混乱していることがわかりました。POの場合、「変数」は何を指しますか?私はそれがだと思いrectます。のコピーがメソッドにrect渡されると言っているのContainsですか?
XTU

51

不純な方法とは、値をそのままにしておくことが保証されていない方法です。

.NET 4では、メソッドと型をで装飾[Pure]して、それらが純粋であることを宣言できます。R#はこれに注意します。残念ながら、それを他の誰かのメンバーに適用することはできません。また、私が知る限り、.NET3.5プロジェクトでタイプ/メンバーが純粋であることをR#に納得させることはできません。(これはいつも野田時間で私を噛みます。)

アイデアは、あなたがメソッド変異する変数呼んでいるが、あなたは読み取り専用フィールドでそれを呼び出す場合、おそらくということですされていないR#はこれについて警告を表示しますので、あなたが望むものをやって。例えば:

public struct Nasty
{
    public int value;

    public void SetValue()
    {
        value = 10;
    }
}

class Test
{
    static readonly Nasty first;
    static Nasty second;

    static void Main()
    {
        first.SetValue();
        second.SetValue();
        Console.WriteLine(first.value);  // 0
        Console.WriteLine(second.value); // 10
    }
}

実際に純粋なすべてのメソッドがそのように宣言されている場合、これは非常に便利な警告になります。残念ながらそうではないので、誤検知がたくさんあります:(


つまり、不純なメソッドは、渡された可変値タイプの基になるフィールドを変更する可能性があるということですか?
酸性2012年

@Acidic:引数の値ではなく(不純なメソッドでも可能です)、呼び出した値です。(メソッドにパラメーターさえない私の例を参照してください。)
Jon Skeet 2012年

2
JetBrains.Annotations.PureAttribute代わりに使用できますSystem.Diagnostics.Contracts.PureAttribute。これらはReSharperコード分析で同じ意味を持ち、.NET 3.5、.NET 4、またはSilverlightで同等に機能するはずです。XMLファイルを使用して所有していないアセンブリに外部から注釈を付けることもできます(ReSharperビンパスのExternalAnnotationsディレクトリを参照してください)。これは非常に便利です。
Julien Lebosquain 2012年

5
@JulienLebosquain:特にオープンソースプロジェクトの場合、ツール固有のアノテーションを追加し始めるのは本当に気が進まないでしょう。オプションとして知っている、しかし...には良い
ジョンスキート

1
実際System.Diagnostics.Contracts.PureAttribute、R#8.2ではこの警告が抑制されなかったことがわかりましJetBrains.Annotations.PureAttributeた。2つの属性の説明も異なりPureます。contracts属性は「結果はパラメーターのみに依存する」ことを意味し、JetBrainsPureは結果の計算に使用されるオブジェクトの状態を除外せずに「目に見える状態変化を引き起こさない」ことを意味します。(しかし、それでもPureこの警告に同じ影響を与えない契約はおそらくバグです。)
Wormbo 2014

15

簡単に言うと、これは誤検知であり、警告は無視しても問題ありません。

より長い答えは、読み取り専用の値型にアクセスするとそのコピーが作成されるため、メソッドによって行われた値への変更はコピーにのみ影響するということです。ReSharperContainsは、それが純粋な方法であることを認識していません(つまり、副作用がありません)。Eric Lippertがここでそれについて話します:読み取り専用構造体の変更


2
完全に理解されるまで、この警告を決して無視しないでください!!! これが安全である良い例の1つは、この構造ですprivate readonly SpinLock _spinLock = new SpinLock();。-このようなロックは完全に役に立たないでしょう(読み取り専用修飾子により、Enterメソッドが呼び出されるたびにオンザフライコピーが作成されるため)
1

11

Reshaprerは、メソッドContainsrect値を変更できると信じているようです。のでrectあるreadonly structC#コンパイラは、値の守備のコピーが変異からメソッドを防止することができるreadonlyフィールドを。基本的に、最終的なコードは次のようになります

Rectangle temp = rect;
if (temp.Contains(point)) {
  ...
}

Resharperはここで警告していますが、一時的に発生したためにすぐに失われるContains可能性がありますrect


つまり、メソッドで実行されるロジックに影響を与えることはなく、呼び出された値が変更されないようにするだけですよね?
酸性2012年

5

不純なメソッドは、副作用が発生する可能性のあるメソッドです。この場合、Resharperは変更される可能性があると考えているようrectです。おそらくそうではありませんが、証拠の連鎖は壊れています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.