14

私は、複雑な値オブジェクトを暗黙的に検証するメカニズムとして構造体を使用したり、より複雑なクラスのジェネリック構造体を使用して有効な値を保証したりしてきました。私はパフォーマンスへの影響について少し無知なので、皆さんが私を助けてくれることを願っています. たとえば、値型ラッパーにドメイン オブジェクトを挿入するようなことをするとしたら、問題が発生するでしょうか? なんで?値型と参照型の違いを理解しています。ここでの目標は、値型のさまざまな動作を活用することです。これを責任を持って行うには、正確に何を調べる必要がありますか?

これは私が考えていたことの非常に基本的な考えです。

public struct NeverNull<T>
    where T: class, new()
{

    private NeverNull(T reference)
    {
        _reference = reference;
    }

    private T _reference;

    public T Reference
    {
        get
        {
            if(_reference == null)
            {
                _reference = new T();
            }
            return _reference;
        }
        set
        {
            _reference = value;
        }
    }

    public static implicit operator NeverNull<T>(T reference)
    {
        return new NeverNull<T>(reference);
    }

    public static implicit operator T(NeverNull<T> value)
    {
        return value.Reference;
    }
}
4

6 に答える 6

11

さて、厄介なことの 1 つは、単純に期待するように動作しないことです。

NeverNull<Foo> wrapper1 = new NeverNull<Foo>();
NeverNull<Foo> wrapper2 = wrapper1;

Foo foo1 = wrapper1;
Foo foo2 = wrapper2;

インスタンスを作成する前に元のバージョンがコピーされたため、 の2 つのインスタンスが作成されます。Foowrapper1

基本的に、変更可能な構造体を扱っています。これは、決して良いことではありません。さらに、私は通常、暗黙的な変換に熱心ではありません。

ここで魔法のようなコードを実現しようとしているように感じます...そして私は一般的にそのようなことに反対しています. 特定のユースケースでは意味があるかもしれませんが、個人的にどこで使用したいかはわかりません.

于 2010-12-09T15:35:30.210 に答える
7

Jon が正しく指摘しているように、ここでの問題は、型の動作が予期しないことであり、遅いことではありません。パフォーマンスの観点からは、参照の周りの構造体ラッパーのオーバーヘッドは非常に低くなければなりません。

やりたいことが null 非許容の参照型を表すことである場合、構造体はそれを行うための合理的な方法です。ただし、「自動作成」機能を失うことで、構造体を不変にする傾向があります。

public struct NeverNull<T> where T: class 
{ 
    private NeverNull(T reference) : this()
    { 
        if (reference == null) throw new Exception(); // Choose the right exception
        this.Reference = reference; 
    } 

    public T Reference { get; private set; }

    public static implicit operator NeverNull<T>(T reference) 
    { 
        return new NeverNull<T>(reference); 
    } 

    public static implicit operator T(NeverNull<T> value) 
    { 
        return value.Reference; 
    } 
}

呼び出し元に有効な参照を提供する責任を負わせます。彼らが1つを「新しく」したいのなら、やらせてください。

一般的な変換演算子を使用すると、予期しない結果が生じる可能性があることにも注意してください。変換演算子の仕様を読み、完全に理解する必要があります。たとえば、「オブジェクト」の周りにnull以外のラッパーを作成してから、そのものを暗黙的にラップ解除変換に変換することはできません。オブジェクトへのすべての暗黙的な変換は、構造体でのボクシング変換になります。C# 言語の組み込み変換を "置き換える" ことはできません。

于 2010-12-09T17:42:53.523 に答える
2

わかりました、上記のメモだけ。

MyStruct st; foo.Bar(st); // st をコピー

たとえば Bar のパラメータがオブジェクトでない限り、これはボクシングではありません。

void Bar(MyStruct parameter){}

値の型をボックス化しません。

C# では、ref または out キーワードを使用しない限り、パラメータはデフォルトで値渡しされます。値で渡されたパラメーターはコピーされます。構造体を渡す場合とオブジェクトを渡す場合の違いは、渡されるものです。値型の場合、実際の値がコピーされます。つまり、新しい値型が作成されるため、コピーが作成されます。参照型では、参照型への参照が渡されます。私が推測する名前の手がかり:)

したがって、ref/out キーワードを使用しない限り構造全体がコピーされるため、構造体のパフォーマンスが低下します。これを広範囲に行う場合は、コードを確認する必要があると思います。

ボックス化は、参照型変数に値型を割り当てるプロセスです。新しい参照型 (オブジェクト) が作成され、値型のコピーがそれに割り当てられます。

元のコードであなたが何をしていたかはある程度わかりましたが、明示的ではなく多くの暗黙的な複雑さを持つ単純な問題を解決しているようです。

于 2010-12-09T15:56:05.913 に答える
2

この質問の答えは、パフォーマンスの議論から離れ、代わりに可変値型の危険性に対処しているようです。

これが役立つと思われる場合に備えて、不変の値型ラッパーを使用して元の例と同様のことを行う、私が一緒に投げた実装を次に示します。

違いは、値の型が参照先のオブジェクトを直接参照していないことです。代わりに、キーと、キーを使用したルックアップ (TryGetValueFunc) またはキーを使用した作成のいずれかを実行するデリゲートへの参照を保持します。(注: 私の元の実装では、IDictionary オブジェクトへの参照を保持するラッパーがありましたが、もう少し柔軟にするために、TryGetValueFunc デリゲートに変更しました。そうしても、なんらかの欠陥が生じることはありませんでした)。

ただし、ラッパーがアクセスする基礎となるデータ構造を操作している場合、予期しない動作が発生する可能性があることに注意してください。

以下は、コンソール プログラムの使用例とともに、完全な動作例です。

public delegate bool TryGetValueFunc<TKey, TValue>(TKey key, out TValue value);

public struct KeyedValueWrapper<TKey, TValue>
{
    private bool _KeyHasBeenSet;
    private TKey _Key;
    private TryGetValueFunc<TKey, TValue> _TryGetValue;
    private Func<TKey, TValue> _CreateValue;

    #region Constructors

    public KeyedValueWrapper(TKey key)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = null;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = tryGetValue;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = null;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = tryGetValue;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _TryGetValue = tryGetValue;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _TryGetValue = tryGetValue;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _TryGetValue = null;
        _CreateValue = createValue;
    }

    #endregion

    #region "Change" methods

    public KeyedValueWrapper<TKey, TValue> Change(TKey key)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _TryGetValue, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, tryGetValue, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _TryGetValue, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, tryGetValue, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, tryGetValue, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, tryGetValue, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, _TryGetValue, createValue);
    }

    #endregion

    public TValue Value
    {
        get
        {
            if (!_KeyHasBeenSet)
                throw new InvalidOperationException("A key must be specified.");

            if (_TryGetValue == null)
                throw new InvalidOperationException("A \"try get value\" delegate must be specified.");

            // try to find a value in the given dictionary using the given key
            TValue value;
            if (!_TryGetValue(_Key, out value))
            {
                if (_CreateValue == null)
                    throw new InvalidOperationException("A \"create value\" delegate must be specified.");

                // if not found, create a value
                value = _CreateValue(_Key);
            }
            // then return that value
            return value;
        }
    }
}

class Foo
{
    public string ID { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var dictionary = new Dictionary<string, Foo>();

        Func<string, Foo> createValue = (key) =>
        {
            var foo = new Foo { ID = key };
            dictionary.Add(key, foo);
            return foo;
        };

        // this wrapper object is not useable, since no key has been specified for it yet
        var wrapper = new KeyedValueWrapper<string, Foo>(dictionary.TryGetValue, createValue);

        // create wrapper1 based on the wrapper object but changing the key to "ABC"
        var wrapper1 = wrapper.Change("ABC");
        var wrapper2 = wrapper1;

        Foo foo1 = wrapper1.Value;
        Foo foo2 = wrapper2.Value;

        Console.WriteLine("foo1 and foo2 are equal? {0}", object.ReferenceEquals(foo1, foo2));
        // Output: foo1 and foo2 are equal? True

        // create wrapper1 based on the wrapper object but changing the key to "BCD"
        var wrapper3 = wrapper.Change("BCD");
        var wrapper4 = wrapper3;

        Foo foo3 = wrapper3.Value;
        dictionary = new Dictionary<string, Foo>(); // throw a curve ball by reassigning the dictionary variable
        Foo foo4 = wrapper4.Value;

        Console.WriteLine("foo3 and foo4 are equal? {0}", object.ReferenceEquals(foo3, foo4));
        // Output: foo3 and foo4 are equal? True

        Console.WriteLine("foo1 and foo3 are equal? {0}", object.ReferenceEquals(foo1, foo3));
        // Output: foo1 and foo3 are equal? False
    }
}

IDictionary<string, Foo>の代わりに使用する代替実装TryGetValueFunc<string, Foo>。使用法コードに入れた反例に注意してください。

public struct KeyedValueWrapper<TKey, TValue>
{
    private bool _KeyHasBeenSet;
    private TKey _Key;
    private IDictionary<TKey, TValue> _Dictionary;
    private Func<TKey, TValue> _CreateValue;

    #region Constructors

    public KeyedValueWrapper(TKey key)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = null;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, IDictionary<TKey, TValue> dictionary)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = dictionary;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = null;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(TKey key, IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = dictionary;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(IDictionary<TKey, TValue> dictionary)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _Dictionary = dictionary;
        _CreateValue = null;
    }

    public KeyedValueWrapper(IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _Dictionary = dictionary;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _Dictionary = null;
        _CreateValue = createValue;
    }

    #endregion

    #region "Change" methods

    public KeyedValueWrapper<TKey, TValue> Change(TKey key)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _Dictionary, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, IDictionary<TKey, TValue> dictionary)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, dictionary, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _Dictionary, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, dictionary, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(IDictionary<TKey, TValue> dictionary)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, dictionary, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, dictionary, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, _Dictionary, createValue);
    }

    #endregion

    public TValue Value
    {
        get
        {
            if (!_KeyHasBeenSet)
                throw new InvalidOperationException("A key must be specified.");

            if (_Dictionary == null)
                throw new InvalidOperationException("A dictionary must be specified.");

            // try to find a value in the given dictionary using the given key
            TValue value;
            if (!_Dictionary.TryGetValue(_Key, out value))
            {
                if (_CreateValue == null)
                    throw new InvalidOperationException("A \"create value\" delegate must be specified.");

                // if not found, create a value and add it to the dictionary
                value = _CreateValue(_Key);
                _Dictionary.Add(_Key, value);
            }
            // then return that value
            return value;
        }
    }
}

class Foo
{
    public string ID { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // this wrapper object is not useable, since no key has been specified for it yet
        var wrapper = new KeyedValueWrapper<string, Foo>(new Dictionary<string, Foo>(), (key) => new Foo { ID = key });

        // create wrapper1 based on the wrapper object but changing the key to "ABC"
        var wrapper1 = wrapper.Change("ABC");
        var wrapper2 = wrapper1;

        Foo foo1 = wrapper1.Value;
        Foo foo2 = wrapper2.Value;

        Console.WriteLine("foo1 and foo2 are equal? {0}", object.ReferenceEquals(foo1, foo2));
        // Output: foo1 and foo2 are equal? True

        // create wrapper1 based on the wrapper object but changing the key to "BCD"
        var wrapper3 = wrapper.Change("BCD");
        var wrapper4 = wrapper3;

        Foo foo3 = wrapper3.Value;
        Foo foo4 = wrapper4.Value;

        Console.WriteLine("foo3 and foo4 are equal? {0}", object.ReferenceEquals(foo3, foo4));
        // Output: foo3 and foo4 are equal? True

        Console.WriteLine("foo1 and foo3 are equal? {0}", object.ReferenceEquals(foo1, foo3));
        // Output: foo1 and foo3 are equal? False


        // Counter-example: manipulating the dictionary instance that was provided to the wrapper can disrupt expected behavior
        var dictionary = new Dictionary<string, Foo>();

        var wrapper5 = wrapper.Change("CDE", dictionary);
        var wrapper6 = wrapper5;

        Foo foo5 = wrapper5.Value;
        dictionary.Clear();
        Foo foo6 = wrapper6.Value;

        // one might expect this to be true:
        Console.WriteLine("foo5 and foo6 are equal? {0}", object.ReferenceEquals(foo5, foo6));
        // Output: foo5 and foo6 are equal? False
    }
}
于 2010-12-10T16:14:19.167 に答える
2

主なペナルティは、構造体のボクシングです。また、それらは値によって渡されるため、メソッドに渡されるときに大きな構造体をコピーする必要があります。

MyStruct st;
foo.Bar(st); // st is copied
于 2010-12-09T15:34:09.587 に答える
1

別のパフォーマンスの問題は、構造体をコレクションに配置するときに発生します。たとえば、があり、リストの最初のアイテムList<SomeStruct>のプロパティを変更するとします。Prop1最初の傾向はこれを書くことです:

List<SomeStruct> MyList = CreateList();
MyList[0].Prop1 = 42;

それはコンパイルされません。この作品を作るためにあなたは書く必要があります:

SomeStruct myThing = MyList[0];
myThing.Prop1 = 42;
MyList[0] = myThing.Prop1;

これにより、2つの問題が発生します(主に)。まず、構造体全体を2回コピーすることになります。1回は作業myThingインスタンスにコピーし、次にリストに戻します。foreach2番目の問題は、コレクションを変更し、列挙子が例外をスローするため、でこれを実行できないことです。

ちなみに、あなたのNeverNull物はかなり奇妙な振る舞いをしています。Referenceプロパティをに設定することができますnull。この声明が非常に奇妙だと私は思います。

var Contradiction = new NeverNull<object>(null);

有効です。

このタイプの構造体を作成しようとする理由を知りたいと思います。

于 2010-12-09T16:01:31.610 に答える