11

これは本当にこの質問の派生物ですが、それはそれ自身の答えに値すると思います。

ECMA-334のセクション15.13によると(usingステートメントでは、以下ではリソース取得と呼ばれます):

リソース取得で宣言されたローカル変数 は読み取り専用であり、初期化子を含める必要があります。++埋め込みステートメントがこれらのローカル変数を(代入またはand演算子を介して)変更しようとしたり、または パラメーター--として渡したりしようとすると、コンパイル時エラーが発生します。refout

これは、以下のコードが違法である理由を説明しているようです。

struct Mutable : IDisposable
{
    public int Field;
    public void SetField(int value) { Field = value; }
    public void Dispose() { }
}

using (var m = new Mutable())
{
    // This results in a compiler error.
    m.Field = 10;
}

しかし、これはどうですか?

using (var e = new Mutable())
{
    // This is doing exactly the same thing, but it compiles and runs just fine.
    e.SetField(10);
}

上記のスニペットはC#で未定義および/または違法ですか?それが合法である場合、このコードと上記の仕様からの抜粋との関係は何ですか?それが違法である場合、なぜそれが機能するのですか?それを可能にする微妙な抜け穴がありますか、それとも単なる運に起因するだけで機能するという事実がありますか(そのため、そのような一見無害に見えるコードの機能に依存するべきではありません)?

4

4 に答える 4

3

私は標準を次のように読みます。

using( var m = new Mutable() )
{
   m = new Mutable();
}

は禁止されています - 明白に見える理由があります。構造体 Mutable が許可されていないのはなぜですか。クラスの場合、コードは合法であり、正常にコンパイルされるためです...(私が知っているオブジェクトタイプ..)

また、値型の内容を変更すると RA が危険にさらされる理由もわかりません。誰か説明してくれませんか?

おそらく、構文チェックを行っている誰かが標準を読み違えているだけです;-)

マリオ

于 2011-01-11T21:53:19.383 に答える
2

この動作は未定義です。C#4.0仕様セクション7.6.4(メンバーアクセス)の最後にあるC#プログラミング言語で、PeterSestoftは次のように述べています。

「フィールドが読み取り専用の場合...結果は値です」という2つの箇条書きは、フィールドに構造体タイプがあり、その構造体タイプに可変フィールドがある場合に少し驚くべき効果があります(推奨される組み合わせではありません。を参照してください。この点に関する他の注釈)。

彼は例を提供します。以下に詳細を表示する独自の例を作成しました。

次に、彼は続けて言います:

少し奇妙なことに、代わりにsがusingステートメントで宣言された構造体型のローカル変数であり、sを不変にする効果もある場合、s.SetX()は期待どおりにsxを更新します。

ここでは、著者の1人がこの動作に一貫性がないことを認めていることがわかります。セクション7.6.4によると、読み取り専用フィールドは値として扱われ、変更されません(コピーが変更されます)。セクション8.13では、ステートメントの使用はリソースを読み取り専用として扱うと説明しているため、次のようになります。

埋め込みステートメントでは、リソース変数は読み取り専用です。

usingステートメント内のリソースは、読み取り専用フィールドのように動作する必要があります。7.6.4の規則に従って、変数ではなく値を処理する必要があります。しかし、驚くべきことに、この例に示されているように、リソースの元の値は変更されます。

    //Sections relate to C# 4.0 spec
    class Test
    {
        readonly S readonlyS = new S();

        static void Main()
        {
            Test test = new Test();
            test.readonlyS.SetX();//valid we are incrementing the value of a copy of readonlyS.  This is per the rules defined in 7.6.4
            Console.WriteLine(test.readonlyS.x);//outputs 0 because readonlyS is a value not a variable
            //test.readonlyS.x = 0;//invalid

            using (S s = new S())
            {
                s.SetX();//valid, changes the original value.  
                Console.WriteLine(s.x);//Surprisingly...outputs 2.  Although S is supposed to be a readonly field...the behavior diverges.
                //s.x = 0;//invalid
            }
        }

    }

    struct S : IDisposable
    {
        public int x;

        public void SetX()
        {
            x = 2;
        }

        public void Dispose()
        {

        }
    }    

状況は奇妙です。結論として、読み取り専用の可変フィールドを作成することは避けてください。

于 2013-02-20T23:01:53.517 に答える
2

要約すると

struct Mutable : IDisposable
{
    public int Field;
    public void SetField( int value ) { Field = value; }
    public void Dispose() { }
}


class Program

{
    protected static readonly Mutable xxx = new Mutable();

    static void Main( string[] args )
    {
        //not allowed by compiler
        //xxx.Field = 10;

        xxx.SetField( 10 );

        //prints out 0 !!!! <--- I do think that this is pretty bad
        System.Console.Out.WriteLine( xxx.Field );

        using ( var m = new Mutable() )
        {
            // This results in a compiler error.
            //m.Field = 10;
            m.SetField( 10 );

            //This prints out 10 !!!
            System.Console.Out.WriteLine( m.Field );
        }



        System.Console.In.ReadLine();
    }

したがって、上で書いたこととは対照的に、関数を使用して using ブロック内の構造体を変更しないことをお勧めします。これは機能しているように見えますが、将来機能しなくなる可能性があります。

マリオ

于 2011-01-12T05:29:23.917 に答える
2

SetField(int)コンパイルして実行する理由は、割り当てrefoutパラメーターの呼び出しではなく、関数呼び出しであると思われます。コンパイラーは、(一般に)SetField(int)が変数を変更するかどうかを知る方法がありません。

仕様によれば、これは完全に合法であるように見えます。

そして、代替案を検討してください。特定の関数呼び出しが値を変更するかどうかを判断するための静的分析は、C# コンパイラでは明らかに法外なコストがかかります。この仕様は、あらゆる場合にそのような状況を回避するように設計されています。

usingもう 1 つの選択肢は、ステートメントで宣言された値型変数でのメソッド呼び出しを C# が許可しないようにすることです。IDisposable構造体に実装することはとにかくトラブルを求めているだけなので、それは悪い考えではないかもしれません。しかし、C# 言語が最初に開発されたとき、彼らは多くの興味深い方法で構造体を使用することに大きな期待を寄せていたと思います (GetEnumerator()最初に使用した例が示すように)。

于 2011-01-11T21:13:46.410 に答える