When does using C# structs (value types) sacrifice performance?
我一直在使用结构作为一种隐式验证复杂值对象的机制,以及围绕更复杂类的通用结构来确保有效值。我对表演的后果有点无知,所以我希望你们都能帮我。例如,如果我要做一些类似于将域对象注入到值类型包装器中的事情,这会导致问题吗?为什么?我了解值类型和引用类型之间的区别,这里我的目标是利用值类型的不同行为。为了负责任地做到这一点,我究竟需要调查什么?
这里有一个非常基本的想法,我在想什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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; } } |
好吧,一件令人讨厌的事情是,这并不像你想象的那样天真:
1 2 3 4 5 | NeverNull<Foo> wrapper1 = new NeverNull<Foo>(); NeverNull<Foo> wrapper2 = wrapper1; Foo foo1 = wrapper1; Foo foo2 = wrapper2; |
这将创建两个
基本上,您要处理的是一个可变结构——这几乎从来都不是一个好东西。另外,我一般不喜欢隐式转换。
感觉就像你想在这里实现神奇的代码…我一般反对这种做法。也许它对您的特定用例是有意义的,但是我想不出我个人想在哪里使用它。
正如jon正确指出的,这里的问题是类型的行为是意外的,而不是缓慢的。从性能的角度来看,引用周围的结构包装器的开销应该非常低。
如果您要做的是表示一个不可为空的引用类型,那么一个结构是一种合理的方法;但是,我倾向于通过丢失"自动创建"功能使结构不可变:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } } |
让调用者负责提供有效的参考;如果他们想"新建"一个,让他们。
还要注意,泛型转换运算符可能会给您带来意外的结果。您应该阅读转换操作符的规范并彻底理解它。例如,不能在"object"周围生成非空包装,然后将该对象隐式转换为展开转换;到object的每个隐式转换都将是结构上的装箱转换。您不能"替换"C语言的内置转换。
这个问题的答案似乎已经从讨论绩效转移到解决可变价值类型的危险。
为了以防万一,这里有一个我拼凑起来的实现,它使用一个不可变的值类型包装器做了类似于原始示例的事情。
不同之处在于,我的值类型没有直接引用它所引用的对象;相反,它持有一个键并引用使用键(TryGetValueFunc)执行查找或使用键创建的委托。(注意:我最初的实现让包装器保存了对IDictionary对象的引用,但我将其更改为TryGetValueFunc委托,只是为了使其更灵活一些,尽管这可能更令人困惑,而且我不能100%确定这样做不会打开某种缺陷)。
但是,请注意,如果您操作包装器访问的底层数据结构,这仍然可能导致意外的行为(取决于您所期望的)。
下面是一个完整的工作示例,以及一个控制台程序的使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | 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 } } |
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | 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 } } |
好的,只是上面的注释。
MyStult ST;foo.bar(st);//复制st
这不是装箱,除非bar的参数是对象。
1 | void Bar(MyStruct parameter){} |
不会对值类型进行框选。
默认情况下,参数通过C中的值传递,除非使用ref或out关键字。复制值传递的参数。传递结构和对象的区别在于传递的是什么。对于值类型,实际值将被复制,这意味着将创建一个新的值类型,因此您将得到一个副本。对于引用类型,将传入对引用类型的引用。我猜这名字的线索是:)
因此,结构的性能会受到影响,因为除非您使用ref/out关键字,否则整个结构都会被复制,如果您要广泛地执行此操作,我认为您的代码需要查看。
装箱是将值类型分配给引用类型变量的过程。将创建新的引用类型(对象),并将值类型的副本分配给它。
我有点了解您在原始代码中所做的工作,但它似乎是用一个具有许多隐式而非显式复杂性的问题来解决一个简单的问题。
主要的惩罚是对结构进行拳击。而且它们是按值传递的,因此传递给方法时必须复制大型结构:
1 2 | MyStruct st; foo.Bar(st); // st is copied |
当您将结构放入集合中时,会出现另一个性能问题。例如,假设您有一个
1 2 | List<SomeStruct> MyList = CreateList(); MyList[0].Prop1 = 42; |
这不会编译。为了完成这项工作,你必须写:
1 2 3 | SomeStruct myThing = MyList[0]; myThing.Prop1 = 42; MyList[0] = myThing.Prop1; |
这导致两个问题(主要)。首先,您将复制整个结构两次:一次复制到工作的
顺便说一下,你的
1 |
是有效的。
我想知道您尝试创建这种类型的结构的原因。