Когда использует С# structs (типы значений) жертвует производительность?
Я играл с структурами как механизм для неявной проверки объектов комплексных значений, а также общих структур вокруг более сложных классов для обеспечения допустимых значений. Я немного неосведомлен о последствиях работы, поэтому я надеюсь, что вы все можете мне помочь. Например, если бы я сделал что-то вроде инъекции объекта домена в оболочку типа значения, это могло бы вызвать проблемы? Зачем? Я понимаю разницу между типами значений и ссылочными типами, и моя цель здесь состоит в том, чтобы использовать различное поведение типов значений. Что именно мне нужно для изучения, чтобы сделать это ответственно?
Вот очень основная идея того, о чем я думал.
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
Ну, одна неприятная вещь в том, что это не ведет себя так, как вы могли бы ожидать от нее наивно:
NeverNull<Foo> wrapper1 = new NeverNull<Foo>();
NeverNull<Foo> wrapper2 = wrapper1;
Foo foo1 = wrapper1;
Foo foo2 = wrapper2;
Это приведет к созданию двух экземпляров Foo
, потому что исходная версия была скопирована, перед тем как wrapper1
создал экземпляр.
В принципе, вы имеете дело с изменяемой структурой, что почти никогда не бывает приятным. Кроме того, я обычно не заинтересован в неявных преобразованиях.
Похоже, вы пытаетесь найти здесь волшебный код... и я вообще против такого. Возможно, это имеет смысл для вашего конкретного случая использования, но я не могу придумать, где я лично хотел бы его использовать.
Ответ 2
Как верно указывает Джон, проблема здесь в том, что поведение типа неожиданно, а не медленное. С точки зрения производительности накладные расходы на обертку структуры вокруг ссылки должны быть очень низкими.
Если то, что вы хотите сделать, состоит в том, чтобы представить ссылочный тип, не содержащий 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;
}
}
Сделать вызывающего абонента ответственным за предоставление действительной ссылки; если они хотят "нового", пусть они.
Обратите также внимание на то, что общие операторы преобразования могут дать вам неожиданные результаты. Вы должны прочитать спецификацию операторов конвертации и понять ее полностью. Например, вы не можете создать ненулевую оболочку вокруг "объекта", а затем эту вещь неявно конвертировать в развернутое преобразование; каждое неявное преобразование в объект будет преобразованием бокса в структуру. Вы не можете "заменить" встроенное преобразование языка С#.
Ответ 3
Основное наказание - бокс для структур. Также они передаются по значению, поэтому большая структура при передаче методу должна быть скопирована:
MyStruct st;
foo.Bar(st); // st is copied
Ответ 4
Хорошо, просто примечание к сказанному выше.
MyStruct st;
foo.Bar(ул);//st скопировано
Это не бокс, если параметр Bar не является объектом, например.
void Bar(MyStruct parameter){}
не будет указывать тип значения.
Параметры передаются по значению в С# по умолчанию, если вы не используете ключевое слово ref или out. Скопированы параметры, переданные по значению. Разница между передачей структуры и объектом - это то, что передается. С типом значения фактическое значение копируется в, что означает, что создается новый тип значения, поэтому вы получаете копию. С ссылочным типом передается ссылка на ссылочный тип. Подсказки в имени, который я предполагаю:)
Таким образом, для структур создается удар производительности, потому что вся структура копируется, если вы не используете ключевое слово ref/out, и если вы делаете это широко, я думаю, что ваш код нуждается в поиске.
Бокс - это процесс присвоения типа значения переменной ссылочного типа. Создается новый ссылочный тип (объект) и копия типа значения, назначенного ему.
Я как бы понял, что вы делали в исходном коде, но, похоже, он решает одну простую проблему с тем, что имеет много неявных, а не явных сложностей.
Ответ 5
Ответы в этом вопросе, похоже, отошли от обсуждения производительности и вместо этого обращаются к опасностям изменяемых типов значений.
На всякий случай, когда вы найдете это полезным, вот реализация, которую я собрал вместе, что-то похожее на ваш оригинальный пример, используя неизменяемую оболочку типа значения.
Разница в том, что мой тип значения напрямую не ссылается на объект, к которому он относится; вместо этого он содержит ключ и ссылки на делегатов, которые выполняют поиск с помощью ключа (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
}
}
Ответ 6
Другая проблема с производительностью возникает, когда вы кладете структуры в коллекции. Например, представьте, что у вас есть 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);
Действителен.
Мне было бы интересно узнать причины, по которым вы пытаетесь создать этот тип структуры.