简体   繁体   中英

Transforming a generic list of one type to another type, where the types are only known at runtime

Essentially, I'm looking for a way to transform GenericList<TInput> to GenericList<TOutput> , where GenericList is a generic list of any type that implements a specific interface, and the types TInput and TOutput are only known at runtime.

Below is a snippet of a class and method that can perform this operation, where TInput and TOutput are supplied at compile time.

 // --------------------------------------------------------------------------------
 /// <summary>This class is an example snippet for transforming generic lists of different types.</summary>
 ///
 /// <remarks></remarks>
 // --------------------------------------------------------------------------------
 public abstract class GenericListHelper<TInput, TOutput>
  where TInput : IGenericObject, new()
  where TOutput : IGenericObject, new()
 {

  // --------------------------------------------------------------------------------
  /// <summary>This method takes in a generic list of an input type
  /// and transforms it a list of the output type.</summary>
  /// 
  /// <param name="inputGenericList">The input list to transform to this list.</param>
  /// <param name="filterElements">Field and property values to exclude from transform.</param>
  // --------------------------------------------------------------------------------
  public static GenericList<TOutput> CreateList(GenericList<TInput> inputGenericList, NameObjectCollection filterElements)
  {
   if (inputGenericList != null)
   {
    GenericList<TOutput> outputGenericList = new GenericList<TOutput>();
    foreach (TInput loopItem in inputGenericList)
    {
     TOutput newItem = new TOutput();
     DataTransformHelper.TransformDataFromObject(loopItem, newItem, filterElements);
     outputGenericList.Add(newItem);
    }
    return outputGenericList;
   }
   return null;
  }
 }

Is there any way of doing something along these lines where TInput and TOutput can be supplied at runtime?

Using reflection in one form or another seems to be the path to get there.

Initially, I tried creating a constructor for GenericList<TInput> that would take a list of type TOutput as a parameter (then I could call Activator.CreateInstance to get the new list).

Alternatively, I tried invoking the above method through reflection, but since that method is flagged ContainsGenericParameters=true , and is flagged as IsGenericMethod=false , I was not able to invoke the method, either through a normal method.Invoke or through a generic method.Invoke (unable to call MakeGenericMethod ).

Isn't that what SelectMany is all about? Say I have two lists of different types, listA, and listB, then listC is a new list, like:

var listC = listA.SelectMany(a => listB, (a, b) => new { a.PropertyA, b.PropertyB });

You said you don't know the types until runtime but that they implement a specific interface, so you don't have to use reflection. So in your case listA would be an IEnumerable and PropertyA and PropertyB would be some properties your interface exposes.

Or if you are using attributes as you mentioned in a comment you can use that where the anonymous type is being created.

If I've understood your problem correctly, you should be able to use Type Converters. However, this will only really be feasible if the lists of possible TInput and TOutput are relatively small and follow a defined mapping. Using a custom Type Converter, you can use the standard methods CanConvertTo, CanConvertFrom, ConvertTo and ConvertFrom to achieve your required conversions. Your implementations of these methods will do the necessary data copying.

Check out the example, How to: Implement a Type Converter

In the process of putting this question together, I think I answered my own question (with help from some other posts here), but I thought I'd throw this out there anyway.

Below is a snippet of some constructors for a GenericList to help with the transformation (the static method above is not used in this process).

 // --------------------------------------------------------------------------------
 /// <summary>This class is used for strongly typed sortable lists of generic
 /// objects (such as data access or business objects).</summary>
 ///
 /// <remarks></remarks>
 // --------------------------------------------------------------------------------
 public class GenericList<T> : IGenericList<T>
           where T : IGenericObject, new()
 {

  // --------------------------------------------------------------------------------
  /// <summary>Base constructor.</summary>
  // --------------------------------------------------------------------------------
  public GenericList()
  {
  }

  // --------------------------------------------------------------------------------
  /// <summary>This constructor takes in a generic list of the same
  /// type and transforms it to this list.</summary>
  /// 
  /// <param name="inputGenericList">The input list to transform to this list.</param>
  /// <param name="filterElements">Field and property values to exclude from transform.</param>
  // --------------------------------------------------------------------------------
  public GenericList(GenericList<T> inputGenericList, NameObjectCollection filterElements)
  {
   if (inputGenericList != null)
   {
    foreach (T loopItem in inputGenericList)
    {
     T newItem = new T();
     DataTransformHelper.TransformDataFromObject(loopItem, newItem, filterElements);
     Add(newItem);
    }
   }
  }

  // --------------------------------------------------------------------------------
  /// <summary>This constructor takes in a generic list of another
  /// type and transforms it to this list.</summary>
  /// 
  /// <param name="inputListElementType">The type of element to be found in the input list.</param>
  /// <param name="inputGenericList">The input list to transform to this list.</param>
  /// <param name="filterElements">Field and property values to exclude from transform.</param>
  // --------------------------------------------------------------------------------
  public GenericList(Type inputListElementType, object inputGenericList, NameObjectCollection filterElements)
  {
   if (inputGenericList != null)
   {
    Type inputListType = typeof(GenericList<>);
    Type combinedType = inputListType.MakeGenericType(inputListElementType);
    IList elements = (IList) Activator.CreateInstance(combinedType, inputGenericList, filterElements);
    foreach (IGenericObject loopItem in elements)
    {
     T newItem = new T();
     DataTransformHelper.TransformDataFromObject(loopItem, newItem, filterElements);
     Add(newItem);
    }
   }
  }
 }

So, the calling code invokes Activator.CreateInstance to create an instance of GenericList<TOutput> , calling the constructor above that takes the type TInput and list of type TInput as an object. That constructor invokes the other constructor to create an instance of GenericList<TInput> . Now the original constructor can use the list of type TInput to transform into the new list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM