Les réponses à cette question semblent s'être éloignées de la discussion sur les performances et abordent plutôt les dangers des types de valeurs mutables. Juste au cas où vous trouveriez ceci utile, voici une implémentation que j'ai lancée ensemble qui fait quelque chose de similaire à votre exemple original en utilisant un wrapper de type valeur immuable. La différence est que mon type de valeur ne fait pas directement référence à l'objet auquel il se rapporte; à la place, il contient une clé et des références aux délégués qui effectuent une recherche en utilisant la clé (TryGetValueFunc) ou un create en utilisant la clé.(Remarque: mon encapsuleur contenait une référence à un objet IDictionary dans mon implémentation d'origine, mais je l'ai remplacé par un délégué TryGetValueFunc juste pour le rendre un peu plus flexible, même si cela peut être plus confus et je ne suis pas sûr à 100% cela n'a pas ouvert une sorte de défaut). Notez toutefois que cela peut entraîner un comportement inattendu (en fonction de ce que vous attendez) si vous manipulez les structures de données sous-jacentes auxquelles accède l'encapsuleur.
Voici une exemple de travail complet, ainsi que d'un exemple d'utilisation d'un programme de console:
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
}
}
mise en œuvre alternative à l'aide IDictionary<string, Foo>
au lieu de TryGetValueFunc<string, Foo>
. Notez le contre-exemple, je mets dans le code d'utilisation:
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
}
}
c'est la 1ère fois, je vois ce type de question – TalentTuner