2010-12-09 2 views
11

Я играл с structs как механизм для неявной проверки объектов комплексного значения, а также общих структур вокруг более сложных классов для обеспечения допустимых значений. Я немного неосведомлен о последствиях работы, поэтому я надеюсь, что вы все можете мне помочь. Например, если бы я сделал что-то вроде инъекции объекта домена в оболочку типа значения, это могло бы вызвать проблемы? Зачем? Я понимаю разницу между типами значений и ссылочными типами, и моя цель здесь состоит в том, чтобы использовать различное поведение типов значений. Что именно мне нужно заглянуть, чтобы сделать это ответственно?Когда использует C# 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; 
    } 
} 
+0

Это первый раз, я вижу этот тип вопроса – TalentTuner 2010-12-09 15:40:38

ответ

10

Ну, одна неприятная вещь в том, что это не ведет себя, как вы могли бы ожидать, что это наивно:

NeverNull<Foo> wrapper1 = new NeverNull<Foo>(); 
NeverNull<Foo> wrapper2 = wrapper1; 

Foo foo1 = wrapper1; 
Foo foo2 = wrapper2; 

Это собирается создать два экземпляров Foo, потому что оригинальная версия была скопирована перед wrapper1 создал экземпляр.

В принципе, вы имеете дело с изменчивой структурой, которая почти никогда не хорошая вещь. Кроме того, я обычно не заинтересован в неявных преобразованиях.

Похоже, вы пытаетесь найти здесь волшебный код ... и я вообще против такого. Возможно, это имеет смысл для вашего конкретного случая использования, но я не могу придумать, где я лично хочу его использовать.

+0

Я знал о поведении, которое вы описали. Однако, если это было нежелательно, я вижу способ обойти «проблему копирования». Вместо экземпляра NeverNull ссылки могут храниться в статическом словаре. Затем NeverNull сохранит хэш-код в словаре. Мне кажется, что это приведет к тому же типу функций без копирования ссылок. Цель неявного приведения заключается в том, чтобы упростить работу с ссылочным типом, сохраняя при этом ограничение, отличное от нуля. – smartcaveman 2010-12-09 15:57:28

+0

На самом деле возникает еще один вопрос. Статические члены структур ведут себя иначе, чем статические члены классов? Я ценю вашу помощь. – smartcaveman 2010-12-09 15:58:45

+1

@smartcaveman: Похоже, вы добавляете слои магии друг на друга ... Я редко нахожу, что я в ситуации, когда я буду доволен созданием нового «пустого» экземпляра, если бы это wasn ' t иначе присутствует. Что касается статических членов - нет, они в основном одинаковы между структурами и классами. – 2010-12-09 16:04:04

2

Основной штраф с боксом для структур. Кроме того, они передаются по значению, так большая структура, когда передается методу придется быть скопирована:

MyStruct st; 
foo.Bar(st); // st is copied 
+0

+1 для решения озабоченности производительности, что и этот вопрос с просьбой о. В этой статье MSDN (http://msdn.microsoft.com/en-us/library/ah19swz4%28VS.71%29.aspx) вкратце обсуждается ситуация, когда было бы более эффективно использовать тип значения, хотя это не прямо обращайтесь к обратному: когда неэффективно использовать тип значения. – 2010-12-09 15:48:43

2

Хорошо, просто примечание о вышесказанном.

MyStruct st; foo.Bar (st); // st скопирован

Это не бокс, если параметр Bar не является объектом, например.

void Bar(MyStruct parameter){} 

не будет указывать тип значения.

Параметры передаются по значению в C# по умолчанию, если вы не используете ключевое слово ref или out. Скопированы параметры, переданные по значению. Разница между передачей структуры и объектом - это то, что передается. С типом значения фактическое значение копируется в, что означает, что создается новый тип значения, поэтому вы получаете копию. С ссылочным типом передается ссылка на ссылочный тип. Подсказки в имени, который я думаю :)

Таким образом, для структур создается такт производительности, потому что вся структура копируется, если вы не используете ключевое слово ref/out, и если вы делаете это широко, я думаю, что ваш код нужно смотреть.

Бокс - это процесс присвоения типа значения переменной ссылочного типа. Создается новый ссылочный тип (объект) и копия присвоенного ему типа значения.

Я вроде как получил то, что вы делали в исходном коде, но, похоже, он решает одну простую проблему с тем, что имеет много неявных, а не явных сложностей.

1

Другая проблема с производительностью возникает, когда вы кладете структуры в коллекции. Например, представьте, что у вас есть 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); 

Действителен.

Мне было бы интересно узнать причины, по которым вы пытаетесь создать такой тип структуры.

7

Как Джон правильно указывает, проблема здесь заключается в том, что поведение типа неожиданным, не то, что это медленно. С точки зрения производительности накладные расходы на обертку структуры вокруг ссылки должны быть очень низкими.

Если то, что вы хотите сделать, состоит в том, чтобы представить ссылочный тип, не содержащий 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; 
    } 
} 

Сделать вызывающую ответственность за предоставление действительных ссылок; если они хотят «нового», пусть они.

Обратите также внимание на то, что общие операторы преобразования могут дать вам неожиданные результаты. Вы должны прочитать спецификацию операторов конвертации и понять ее полностью. Например, вы не можете создать ненулевую оболочку вокруг «объекта», а затем эту вещь неявно конвертировать в развернутое преобразование; каждое неявное преобразование в объект будет преобразованием бокса в структуру. Вы не можете «заменить» встроенное преобразование языка C#.

2

Ответы в этом вопросе, похоже, отошли от обсуждения производительности и вместо этого обращаются к опасностям изменяемых типов значений.

На всякий случай, если вы сочтете это полезным, вот реализация, которую я собрал вместе, что делает что-то похожее на ваш оригинальный пример, используя неизменяемую оболочку типа значения.

Разница в том, что мой тип значения напрямую не ссылается на объект, к которому он относится; вместо этого он содержит ключ и ссылки на делегатов, которые выполняют поиск с помощью ключа (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 
    } 
}