什么时候使用C#结构(值类型)牺牲性能?
-
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;
这将创造 二 实例 Foo
因为原始版本是复制的,所以 wrapper1
创建一个实例。
基本上,您正在处理一个可变的结构 - 几乎是 绝不 一件好事。此外,我通常不热衷于隐式转换。
感觉就像您在这里试图在这里实现魔法代码……我通常反对这种事情。 也许 对于您的特殊用例,这是有意义的,但是我想不出我个人想在哪里使用它。
其他提示
正如乔恩(Jon)正确指出的那样,这里的问题是类型的行为是 意外, ,不是那样 减缓. 。从性能的角度来看,引用周围结构包装器的开销应该非常低。
如果您想做的是代表不可解开的参考类型,那么结构是一种合理的方法。但是,我倾向于通过失去“自动创建”功能来使结构不变:
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){}
不会包装值类型。
默认情况下,默认情况下,参数通过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;
这引起了两个问题(主要是)。首先,您最终将整个结构复制两次:一旦进入工作 myThing
实例,然后返回列表。第二个问题是您不能在 foreach
因为它更改了集合,并会导致枚举者抛出异常。
顺便说一句,你 NeverNull
事情有一个相当奇怪的行为。可以设置 Reference
财产为 null
. 。这句话使我感到非常奇怪:
var Contradiction = new NeverNull<object>(null);
已验证。
我很想知道您尝试创建这种类型的结构的原因。