2010-12-09 143 views
11

我一直在玩结构作为隐式验证复杂值对象的机制,以及围绕更复杂类的通用结构以确保有效值。我对表演的后果有点无知,所以我希望你们都能帮助我。例如,如果我要做一些事情,比如将一个域对象注入一个值类型的包装器,会导致问题吗?为什么?我理解值类型和引用类型之间的区别,我的目标是利用值类型的不同行为。为了负责任地做到这一点,我需要研究什么?何时使用C#结构(值类型)会牺牲性能?

这是我想到的一个非常基本的想法。

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:这听起来像是你在彼此之上添加了许多魔法......我很少发现我处于一种情况,即如果它不是一个新的“空”实例,否则存在。至于静态成员 - 不,他们在结构和类之间基本相同。 – 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){} 

不会填充值类型。

除非使用ref或out关键字,否则参数在默认情况下通过c#值传递。按值传递的参数被复制。传递结构和对象之间的区别是传递的。使用值类型时,实际值将被复制,这意味着会创建一个新的值类型,因此最终会得到一个副本。使用引用类型时,引用类型的引用被传入。名称中的线索我猜:)

因此,结构会出现性能问题,因为除非使用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

由于乔恩正确地指出,现在的问题是,该类型的行为是意外,而不是它是。从性能角度来看,引用周围的结构包装的开销应该非常低。

如果你想要做的是代表一个不可为空的引用类型,那么一个结构是一个合理的方式来做到这一点;不过,我会倾向于使结构不变输了“自动创建”功能:

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 
    } 
} 
相关问题