C#structs(valueタイプ)の使用パフォーマンスを犠牲にするのはいつですか?
-
10-10-2019 - |
質問
私は、複雑な値オブジェクトを暗黙的に検証するメカニズムとして、および有効な値を確保するために、より複雑なクラスをめぐる一般的な構造体として構造を再生してきました。私はパフォーマンスの結果について少し無知ですので、皆さんが私を助けることができることを願っています。たとえば、ドメインオブジェクトを値型ラッパーに注入するようなことをする場合、それは問題を引き起こしますか?なんで?私は価値タイプと参照タイプの違いを理解しています。ここでの私の目標は、値タイプの異なる動作を活用することです。これを責任を持って行うためには、正確に何を調べる必要がありますか?
これが私が考えていたことの非常に基本的なアイデアです。
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;
}
}
解決
まあ、一つの厄介なことは、あなたがそれが素朴に期待されるかもしれないので、これは振る舞わないということです:
NeverNull<Foo> wrapper1 = new NeverNull<Foo>();
NeverNull<Foo> wrapper2 = wrapper1;
Foo foo1 = wrapper1;
Foo foo2 = wrapper2;
それが作成されます 2 のインスタンス Foo
元のバージョンが以前にコピーされたためです wrapper1
インスタンスを作成しました。
基本的に、あなたは可変構造体を扱っています - それはほとんどです 一度もない 良いことです。さらに、私は一般的に暗黙的な変換に熱心ではありません。
ここで魔法のように見えるコードを達成しようとしているように感じます...そして、私は一般的にそのようなことに反対しています。 多分 それはあなたの特定のユースケースにとって理にかなっていますが、私は個人的にそれを使用したい場所を考えることはできません。
他のヒント
ジョンが正しく指摘しているように、ここでの問題は、タイプの動作が 予期しない, 、そうではありません スロー. 。パフォーマンスの観点から見ると、参照周辺のstructラッパーのオーバーヘッドは非常に低くする必要があります。
あなたがしたいことが非微細な参照タイプを表すことである場合、構造体はそれを行う合理的な方法です。ただし、「自動作成」機能を失うことで、構造体を不変にする傾向があります。
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;
}
}
発信者に有効な参照を提供する責任を負わせます。彼らが「新しく」したいなら、それらをしましょう。
また、一般的な変換演算子は予期しない結果を与えることができることに注意してください。コンバージョンオペレーターの仕様を読み、それを徹底的に理解する必要があります。たとえば、「オブジェクト」の周りに非ヌルラッパーを作成してから、そのことを暗黙的にアンラッピング変換に変換することはできません。オブジェクトへのすべての暗黙的な変換は、構造体のボクシング変換になります。 C#言語の組み込み変換を「置き換える」ことはできません。
主なペナルティは次のとおりです ボクシング 構造体の場合。また、それらは価値によって渡されますので、方法に渡されると大きな構造体は コピー:
MyStruct st;
foo.Bar(st); // st is copied
わかりました、上記のメモだけです。
mystruct st; foo.bar(st); // STがコピーされます
たとえば、barのパラメーターがオブジェクトでない限り、これはボクシングではありません。
void Bar(MyStruct parameter){}
値タイプをボックス化しません。
REFまたはOUTキーワードを使用しない限り、パラメーターはデフォルトでC#の値で渡されます。値で渡されたパラメーターがコピーされます。構造体とオブジェクトを渡すことの違いは、渡されるものです。値タイプを使用すると、実際の値がコピーされます。つまり、新しい値タイプが作成されるため、コピーが作成されます。参照型では、参照タイプへの参照が渡されます。名前の手がかりが私が推測します:)
そのため、ref/outキーワードを使用しない限り、構造全体がコピーされるため、構造体のパフォーマンスヒットがあり、それを広範囲に行っている場合、コードを調べる必要があると思います。
ボクシングは、値タイプを参照型変数に割り当てるプロセスです。新しい参照タイプ(オブジェクト)が作成され、それに割り当てられた値タイプのコピーが作成されます。
元のコードであなたがしていたことを手に入れましたが、明示的な複雑さではなく多くの暗黙の問題を抱えている単純な問題を解決しているようです。
この質問の答えは、パフォーマンスについて議論することから遠ざかっているようで、代わりに可変価値のタイプの危険性に取り組んでいます。
これが便利だと思う場合に備えて、不変の値タイプのラッパーを使用して元の例に似た何かを行う私が一緒に投げた実装を次に示します。
違いは、私の値タイプがそれが言及するオブジェクトを直接参照しないことです。代わりに、キーを保持し、キー(TryGetValueFunc)を使用してルックアップを実行する代表者への参照またはキーを使用して作成します。 (注:私の元の実装では、ラッパーがIDICTIONARYオブジェクトへの参照を保持していましたが、もう少し柔軟にするためにTryGetValueFuncのデリゲートに変更しましたが、それはより混乱するかもしれませんが、私は100%確信していませんそうすることで、何らかの欠陥が開かれませんでした)。
ただし、ラッパーがアクセスする基礎となるデータ構造を操作している場合、これはまだ予期しない動作をもたらす可能性があることに注意してください(予想されるものによって異なります)。
以下は、コンソールプログラムの使用例とともに、完全な作業例です。
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
}
}
別のパフォーマンスの問題は、コレクションに構造体を入れるときに発生します。たとえば、あなたが持っていると想像してください List<SomeStruct>
, 、そしてあなたは変更したい Prop1
リスト内の最初のアイテムのプロパティ。最初の傾向はこれを書くことです:
List<SomeStruct> MyList = CreateList();
MyList[0].Prop1 = 42;
それはコンパイルされません。この作業を行うには、次のことを書かなければなりません。
SomeStruct myThing = MyList[0];
myThing.Prop1 = 42;
MyList[0] = myThing.Prop1;
これにより、2つの問題が発生します(主に)。まず、構造全体を2回コピーすることになります。 myThing
インスタンス、そしてリストに戻ります。 2番目の問題は、あなたがこれを行うことができないことです foreach
コレクションが変更され、列挙器が例外をスローするためです。
ちなみに、あなた NeverNull
物事にはかなり奇妙な動作があります。設定することは可能です Reference
プロパティへ null
. 。この声明は非常に奇妙なことです。
var Contradiction = new NeverNull<object>(null);
有効です。
このタイプの構造体を作成しようとする理由を知りたいと思います。