2013-06-30 58 views
4

我在寫一種特殊的System.IO.BinaryWriter。這位作家應該能夠處理整數類型,包括Enum也是這些類型的集合實現泛型方法來處理不同整型的集合的正確方法是什麼?

abstract class MyBinaryWriter 
{ 
    // ... 

    #region Methods: Basic Types: Writing 
    public abstract void Write(byte value); 
    public abstract void Write(ushort value); 
    public abstract void Write(uint value); 
    public abstract void Write(ulong value); 
    public abstract void Write(string value); 
    #endregion 

    #region Methods: Complex Types: Writing 
    public virtual void Write<T>(ICollection<T> collection) 
    { 
     // first write the 32-bit-unsigned-length prefix 
     if (collection == null || collection.Count == 0) 
     { 
      Write((uint)0); 
     } 
     else 
     { 
      Write((uint)collection.Count); 

      // then write the elements, if any 
      foreach (var item in collection) 
       ; // What here? Obviously Write(item) doesn't work... 
     } 
    } 

    // ... 
} 

解決此問題的最佳方法是什麼?使用泛型的解決方案比爲每個整型和每個枚舉類型編寫一個重載要好得多。可能的解決方案如下,但我不喜歡這麼多,並有潛在的性能問題。要做到這一點

#region Methods: Complex Types: Writing 
    public virtual void Write<T>(ICollection<T> collection) where T : IConvertible 
    { 
     // first write the 32-bit-unsigned-length prefix 
     if (collection == null || collection.Count == 0) 
     { 
      Write((uint)0); 
     } 
     else 
     { 
      Write((uint)collection.Count); 

      // get the method for writing an element 
      Action<T> write = null; 
      var type = typeof(T); 
      if (type.IsEnum) 
       type = Enum.GetUnderlyingType(type); 

      switch (Type.GetTypeCode(type)) 
      { 
       case TypeCode.Byte: 
       case TypeCode.SByte: 
        write = (x => Write((byte)(IConvertible)x.ToByte(null))); 
        break; 

       case TypeCode.Int16: 
       case TypeCode.UInt16: 
        write = (x => Write((ushort)(IConvertible)x.ToUInt16(null))); 
        break; 

       case TypeCode.Int32: 
       case TypeCode.UInt32: 
        write = (x => Write((uint)(IConvertible)x.ToUInt32(null))); 
        break; 

       case TypeCode.Int64: 
       case TypeCode.UInt64: 
        write = (x => Write((ulong)(IConvertible)x.ToUInt64(null))); 
        break; 

       default: 
        Debug.Fail("Only supported for integral types."); 
        break; 
      } 

      // then write the elements, if any 
      foreach (var item in collection) 
       write(item); 
     } 
    } 
+0

我編輯了你的標題。請參閱:「[應該在其標題中包含」標籤「](http://meta.stackexchange.com/questions/19190/)」,其中的共識是「不,他們不應該」。 –

回答

1

一種方法是編譯表達式:

// helper classes which compiles a fast, type-safe delegate for writing various types 
static class MyBinaryWriterHelper<T> { 
    public static readonly Action<MyBinaryWriter, T> WriteAction; 

    // this initialization is a bit expensive, but it will occur only once 
    // for each writable type T and will occur lazily 
    static { 
     // find the existing Write(T) on the MyBinaryWriter type 
     var writeMethod = typeof(MyBinaryWriter).GetMethods() 
      .FirstOrDefault(m => m.Name == "Write" 
       && m.GetArguments().Length == 1 
       && m.GetArguments()[0](p => p.ParameterType == typeof(T) 
     ); 

     // if there is no such method, fail 
     if (writeMethod == null) { throw ... } 

     // build up an expression (writer, t) => writer.Write(t) 
     var writerParam = Expression.Parameter(typeof(MyBinaryWriter)); 
     var tParam = Expression.Parameter(typeof(T)); 
     var call = Expression.Call(writerParam, writeMethod, tParam); 
     var lambda = Expression.Lambda<Action<MyBinaryWriter, T>>(call, new[] { writerParam, tParam }); 

     // compile the expression to a delegate, caching the result statically in the 
     // readonly WriteAction field 
     WriteAction = lambda.Compile(); 
    } 
} 

// then in your writer class 
public void Write<T>(IEnumerable<T> collection) { 
    // other collection writing logic (e. g. writing the count) ... 

    // to write out the items, just use the static action field 
    foreach (var t in collection) { 
     MyBinaryWriterHelper<T>.WriteAction(this, t); 
    } 
} 

雖然沒有辦法使用泛型執行這一類型是「數字」,你可以使用IConvertible(如您例如代碼)作爲一個鬆散的約束,爲此增加了額外的編譯時安全性。

+0

這正是我想要的方法。感謝您提供即可使用的代碼。非常有趣的是你緩存編譯lambda的方式。在我看來,目前是最好的答案。 – unlikely

0

我能想到的最好的辦法是使用dynamic

public void Write<T>(ICollection<T> collection) { 
    dynamic self = this; 
    foreach (var value in collection) { 
     self.Write(value); 
    } 
} 

動態調用has been discussed by Eric的性能。簡言之,其性能與使用編譯的表達式樹相同。

+0

這真是太棒了!值得注意的是,在其他海報的基準測試中,編譯後的表達式樹仍然是動態方法的兩倍以上。在大多數情況下,這顯然不重要,但是一個自定義的二進制作家看起來像是一個可能的地方。 – ChaseMedallion

+0

動態的使用非常簡單,並提供了一個很好的原型和檢查代碼的方法。但是根據我的需要,正如@ChaseMedallion所寫,我認爲我需要一個更好的表現策略。謝謝。 – unlikely

相關問題