什么时候使用C#结构(值类型)会牺牲性能?
我一直在使用结构作为隐式验证复杂值对象的机制,以及围绕更复杂类的通用结构来确保有效值。我对表演的后果有点无知,所以我希望你们都能帮助我。例如,如果我要做一些类似于将域对象注入值类型包装器的事情,这会导致问题吗?为什么?我理解值类型和引用类型之间的区别,我的目标是利用值类型的不同行为。为了负责任地做到这一点,我到底需要调查什么 这是我想的一个非常基本的想法什么时候使用C#结构(值类型)会牺牲性能?,c#,performance,struct,non-nullable,value-type,C#,Performance,Struct,Non Nullable,Value Type,我一直在使用结构作为隐式验证复杂值对象的机制,以及围绕更复杂类的通用结构来确保有效值。我对表演的后果有点无知,所以我希望你们都能帮助我。例如,如果我要做一些类似于将域对象注入值类型包装器的事情,这会导致问题吗?为什么?我理解值类型和引用类型之间的区别,我的目标是利用值类型的不同行为。为了负责任地做到这一点,我到底需要调查什么 这是我想的一个非常基本的想法 public struct NeverNull<T> where T: class, new() { priva
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;
}
}
public struct NeverNull
其中T:class,new()
{
私有NeverNull(T参考)
{
_参考=参考;
}
私人T_参考;
公众咨询
{
得到
{
如果(_reference==null)
{
_参考=新的T();
}
返回_引用;
}
设置
{
_参考=价值;
}
}
公共静态隐式运算符NeverNull(T引用)
{
返回新的NeverNull(参考);
}
公共静态隐式运算符T(NeverNull值)
{
返回值.引用;
}
}
主要惩罚是对结构进行装箱。它们也是按值传递的,因此当传递给方法时,必须复制大型结构:
MyStruct st;
foo.Bar(st); // st is copied
好吧,一件令人讨厌的事情是,这并不像你天真地期望的那样:
NeverNull<Foo> wrapper1 = new NeverNull<Foo>();
NeverNull<Foo> wrapper2 = wrapper1;
Foo foo1 = wrapper1;
Foo foo2 = wrapper2;
NeverNull wrapper1=新的NeverNull();
NeverNull wrapper2=wrapper1;
Foo foo1=包装器1;
Foo foo2=包装器2;
这将创建Foo
的两个实例,因为原始版本是在wrapper1
创建实例之前复制的
基本上,你要处理的是一个可变结构——这几乎从来都不是一件好事。此外,我一般不喜欢隐式转换
这感觉就像你在试图实现神奇的代码在这里。。。我通常反对这种事情。也许它对于您的特定用例是有意义的,但我想不出我个人想在哪里使用它。好的,只需要上面的一个注释 MyStruct街; 富吧(圣);//st是复制的 这不是装箱,除非Bar的参数是object
void Bar(MyStruct parameter){}
不会将值类型设置为框
默认情况下,参数按c#中的值传递,除非使用ref或out关键字。复制按值传递的参数。传递结构和对象之间的区别在于传递的内容。对于值类型,实际值将被复制进来,这意味着将创建一个新的值类型,因此最终将得到一个副本。对于引用类型,传入对引用类型的引用。我猜名字中的线索:)
因此,结构的性能会受到影响,因为除非您使用ref/out关键字,否则整个结构都会被复制。如果您广泛使用ref/out关键字,我认为您的代码需要查看
装箱是将值类型指定给引用类型变量的过程。将创建一个新的引用类型(对象),并将值类型的副本指定给它
我大致了解了您在原始代码中所做的工作,但它似乎解决了一个简单的问题,其中包含许多隐式而非显式的复杂性。在集合中放置结构时,会出现另一个性能问题。例如,假设您有一个
列表
,并且希望修改列表中第一项的Prop1
属性。最初倾向于这样写:
List<SomeStruct> MyList = CreateList();
MyList[0].Prop1 = 42;
这会导致两个问题(主要是)。首先,将整个结构复制两次:一次复制到正在工作的myThing
实例中,然后返回列表。第二个问题是不能在foreach
中执行此操作,因为它会更改集合并导致枚举数引发异常
顺便说一下,你的NeverNull
东西有一个相当奇怪的行为。可以将Reference
属性设置为null
。我感到非常奇怪的是,这种说法:
var Contradiction = new NeverNull<object>(null);
var=new NeverNull(空);
这是有效的
我想知道您尝试创建这种类型的结构的原因。正如Jon正确指出的那样,这里的问题是类型的行为是意外的,而不是缓慢的。从性能的角度来看,围绕引用的结构包装器的开销应该非常低 如果您想做的是表示一个不可为null的引用类型,那么struct是一种合理的方法;但是,我倾向于通过丢失“自动创建”功能使结构不可变:
public struct NeverNull其中T:class
{
private NeverNull(T reference):this()
{
if(reference==null)抛出新异常();//选择正确的异常
这个。参考=参考;
}
公共T引用{get;私有集;}
公共静态隐式运算符NeverNull(T引用)
{
返回新的NeverNull(参考);
}
公共静态隐式运算符T(NeverNull值)
{
返回值.引用;
}
}
让来电者负责提供有效的参考资料;如果他们想要“新”的,就让他们来吧
还要注意,泛型转换运算符可能会给您带来意外的结果。您应该阅读有关转换运算符的规范并彻底理解它。例如,您不能在“object”周围创建一个非null包装,然后将该对象隐式转换为展开转换;对对象的每个隐式转换都将是结构上的装箱转换。不能“替换”C语言的内置转换<
var Contradiction = new NeverNull<object>(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;
}
}
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
}
}
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
}
}