12

シンプルで不変の値の型がある状況があります。

public struct ImmutableStruct
{
    private readonly string _name;

    public ImmutableStruct( string name )
    {
        _name = name;
    }

    public string Name
    {
        get { return _name; }
    }
}

この値の型のインスタンスをボックス化する場合、通常、ボックス化したものはボックス化解除を行ったときに同じ結果になると予想します。驚いたことに、そうではありません。Reflection を使用すると、ボックスに含まれるデータを再初期化することで、誰かがボックスのメモリを簡単に変更できます。

class Program
{
    static void Main( string[] args )
    {
        object a = new ImmutableStruct( Guid.NewGuid().ToString() );

        PrintBox( a );
        MutateTheBox( a );
        PrintBox( a );;
    }

    private static void PrintBox( object a )
    {
        Console.WriteLine( String.Format( "Whats in the box: {0} :: {1}", ((ImmutableStruct)a).Name, a.GetType() ) );
    }

    private static void MutateTheBox( object a )
    {
        var ctor = typeof( ImmutableStruct ).GetConstructors().Single();
        ctor.Invoke( a, new object[] { Guid.NewGuid().ToString() } );
    }
}

出力例:

ボックスの内容: 013b50a4-451e-4ae8-b0ba-73bdcb0dd612 :: ConsoleApplication1.ImmutableStruct ボックスの内容: 176380e4-d8d8-4b8e-a85e-c29d7f09acd0 :: ConsoleApplication1.ImmutableStruct

(実際には、MSDN に、これが意図された動作であることを示す小さなヒントがあります)

なぜ CLR では、ボックス化された (不変の) 値型をこの微妙な方法で変更できるのでしょうか? readonly が保証されないことはわかっています。また、「従来の」リフレクションを使用すると、値のインスタンスを簡単に変更できることもわかっています。ボックスへの参照がコピーされ、突然変異が予期しない場所に現れると、この動作が問題になります。

私が考えていることの 1 つは、System.Reflection APIobjectのみで動作するため、これにより値型で Reflection を使用できるようになることです。ただし、値の型を使用すると、リフレクションはバラバラになりNullable<>ます (値がない場合は、null にボックス化されます)。ここでの話は何ですか?

4

3 に答える 3

15

CLR に関する限り、ボックスは不変ではありません。実際、C++/CLI では、それらを直接変更する方法があると思います。

ただし、C# では、ボックス化解除操作は常にコピーを取得します。CLRではなく、ボックスの変更を防止するのは C#言語です。IL unbox 命令は、型指定されたポインターをボックスに提供するだけです。ECMA-335のパーティション III のセクション 4.32 (unbox命令) から:

unbox 命令は、値型のボックス化された表現である obj (O 型) を、そのボックス化されていない形式である valueTypePtr (制御された可変性マネージド ポインター (§1.8.1.2.2)、型 &) に変換します。valuetype は、メタデータ トークン (typeref、typedef、または typespec) です。objに含まれるvaluetypeの型は、verifier-assignable-to valuetype でなければなりません。

boxオブジェクトで使用するために値型のコピーを作成する必要がある とは異なり、オブジェクトから値型をコピーする必要unboxはありません。通常、ボックス化されたオブジェクト内に既に存在する値型のアドレスを計算するだけです。

C# コンパイラは常に IL を生成し、その結果としてunboxコピー操作が続きます。または、unbox.anyその後unboxldobj. 生成された IL はもちろん C# 仕様の一部ではありませんが、これは (C# 4 仕様のセクション 4.3):

non-nullable-value-typeへのボックス化解除操作は、最初にオブジェクト インスタンスが指定されたnon-nullable-value-typeのボックス化された値であることを確認し、次にインスタンスから値をコピーすることで構成されます。

nullable-typeへのボックス化解除は、ソース オペランドが の場合はnullable-typeの null 値を生成し、そうでない場合nullは、オブジェクト インスタンスをnullable-typeの基になる型にボックス化解除した結果をラップします。

この場合、リフレクションを使用しているため、C# によって提供される保護をバイパスしています。(これもリフレクションの特に奇妙な使用法です。私は言わなければなりません...ターゲットインスタンスの「上で」コンストラクターを呼び出すことは非常に奇妙です-これまでに見たことはないと思います。)

于 2011-08-22T16:56:25.063 に答える
3

追加するだけです。

IL では、「安全でない」(検証不可能な読み取り) コードを使用すると、ボックス化された値を変更できます。

C# に相当するものは次のようなものです。

unsafe void Foo(object o)
{
  void* p = o;
  ((int*)p) = 2;
}

object a = 1;
Foo(a);
// now a is 2
于 2011-08-22T17:09:08.680 に答える
0

値型のインスタンスは、次の場合にのみ不変と見なされます。

  1. デフォルトのインスタンスと何らかの形で区別できる構造のインスタンスを作成する手段はありません。たとえば、フィールドを持たない構造体は、変更するものが何もないため、不変であると合理的に見なすことができます。
  2. インスタンスを保持するストレージの場所は、それを決して変更しないものによって非公開に保持されます。

最初のシナリオはインスタンスではなく型のプロパティになりますが、「可変性」の概念はステートレス型にはあまり関係ありません。これは、そのような型が役に立たない (*) ことを意味するのではなく、可変性の概念がそれらに無関係であることを意味します。それ以外の場合、任意の状態を保持する構造体型は、そうでないふりをしていても変更可能です。皮肉なことに、構造体を「不変」にしようとせず、単にそのフィールドを公開した場合 (そして、値を設定するためにコンストラクターではなくファクトリ メソッドを使用した可能性があります)、その「コンストラクター」を介して構造体インスタンスを変更すると、動作しません。

new(*) フィールドを持たない構造体型は、インターフェイスを実装し、制約を満たすことができます。渡されたジェネリック型の静的メソッドを使用することはできませんが、インターフェイスを実装する自明な構造を定義し、その構造の型を新しいダミー インスタンスを作成してそのメソッドを使用できるコードに渡すことができます)。たとえば、メソッドが実行される型FormattableInteger<T> where T:IFormatableIntegerFormatter,new()を定義できます。 このようなアプローチを使用すると、20,000 の配列がある場合、整数を格納するためのデフォルトのメソッドは、20,000 回格納されるのではなく、型の一部として 1 回格納されます。 -インスタンスごとに 1 回。ToString()T newT = new T(); return newT.Format(value);FormattableInteger<HexIntegerFormatter>

于 2012-02-26T20:45:18.003 に答える