What is the correct way to implement a universal method for processing collections of different integral types?

I am writing some feature of System.IO.BinaryWriter . This writer should be able to handle integral types, including Enum and , as well as the collection of these types .

 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... } } // ... } 

What is the best approach to solve this problem? There is a better solution using generics than writing overloads for each type of integral, and each type of enum that I want to process? A possible solution follows, but I don’t like it so much and have potential performance problems.

  #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); } } 
+4
source share
2 answers

One way to do this is with compiled expressions:

 // 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 (eg writing the count) ... // to write out the items, just use the static action field foreach (var t in collection) { MyBinaryWriterHelper<T>.WriteAction(this, t); } } 

While it is not possible to use generics to ensure that the type is "numeric", you can use IConvertible (as in your code example) as a free restriction for this purpose, to add extra security at compile time.

+1
source

Best of all, I can use dynamic :

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

Dynamic call performance was discussed by Eric . In short, its performance is somewhat equal to using a compiled expression tree.

0
source

Source: https://habr.com/ru/post/1488970/


All Articles