简体   繁体   中英

c# Dynamic casting a List

I have some legacy code where I'm trying to condense and remove alot of redundant repetition. I have a List<MessageItemViewModel> as a parameter to a method being called from an ASP.NET MVC POST action. The actual list items will be a subclass of MessageItemViewModel , eg. MessageItemA1ViewModel . In the method being called this list was being cast like so:

switch (level1Code)
            {
                case "A1":
                    {
                        var list = messages.Cast<MessageItemA1ViewModel>().ToList();
                        file = printHelper.Print(list).Save(exportFormat);
                    }
                    break;
                case "A2":
                    {
                        var list = messages.Cast<MessageItemA2ViewModel>().ToList();
                        file = printHelper.Print(list).Save(exportFormat);
                    }
                    break;
                case "A3":

There's about 80 cases in all but I want to reduce it using a dictionary:

private readonly Dictionary<string, Type> _messageItemMap = new 
    Dictionary<string, Type>
    {
        {"A1", typeof(MessageItemA1ViewModel)},
        {"A2", typeof(MessageItemA2ViewModel)},
        {"A3", typeof(MessageItemA3ViewModel)},

and cast the list items to the actual underlying type dynamically. So I've followed examples on SO and the approach works well by declaring a static method and casting against an item in my list:

public static class Ext
{
    public static T Cast<T>(this object entity) where T : class
    {
        return entity as T;
    }
}


public class ExportHelper
{
    public byte[] GetFile(string level1Code, string title, List<MessageItemViewModel> messages, ExportFormat exportFormat)
    {
        .
        .
        .

        var castMethod = typeof(Ext).GetMethod("Cast", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(_messageItemMap[level1Code]);
        var anItemType = castMethod.Invoke(null, new object[] { messages[0] });

anItemType is correctly of the actual type I'm casting to. In the immediate window when debugging a call to anItemType is MessageItemA2ViewModel returns true. BUT I need to cast all the items so I tried this:

var castMethod = typeof(Ext).GetMethod("Cast", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(_messageItemMap[level1Code]);
var theMessageItemModel = messages
    .Select(p => castMethod.Invoke(null, new object[] { p }))
    .Where(p => p != null)
    .ToList();

This initially seemed to work as hovering over theMessageItemModel debugging showed the values as the type I cast to, but that's just the magic of the Visual Studio tooling. Inspecting items in the immediate window as previously the items are actually defined as System.Object and also I see the return types of the Linq calls return List<Object> . This is an issue later because theMessageItemModel is passed to another library generic method where TypeDescriptor.GetProperties(typeof(T)) is called in order to determine the shape of the POCO to do some further processing. When passed in as a List<Object> 0 properties are found but if I can get the list passed in as one of the subclasses, eg. List<MessageItemA1ViewModel> then it will return properties.

Can anyone suggest how I should use my Cast RuntimeMethodInfo to create a correctly type List of the Type matching the level1Code in my Type dictionary?

===Update=== Actually one thing I didn't mention that because the library method I'm calling is generic I've had to reflect that as well to pass the dynamically cast type through to the Print method:

var printMethod = printHelper.GetType().GetMethod("Print").MakeGenericMethod(theMessageItemModel.GetType().GetGenericArguments().Single());
var docRender = printMethod.Invoke(printHelper, new object[] { theMessageItemModel }) as IDocumentRender;
file = docRender.Save(exportFormat);

The theMessageItemModel.GetType().GetGenericArguments().Single() call is where System.Object is being found and passed through, because of my Linq call re-casting back to List<System.Object>

Ok, So I had a deeper think about the LINQ statement. Because at compile time what's coming back from my castMethod can't be known then the LINQ could only resolve as far as Object so I needed the List being enumerated to actually be able to be resolved when run. So I realised I can push the LINQ into my Cast method:

public static IEnumerable<T> CastList<T>(this IEnumerable<object> entity) where T : class
{
    var ret = entity
        .Select(e => e as T)
        .ToList();

    return ret;
}

When calling this it returns for example List<MessageItemA2ViewModel> and now my call to theMessageItemModel.GetType().GetGenericArguments().Single() gives me the subclass type actually in the List so the Print method is called correctly:

var castListMethod = typeof(Ext).GetMethod("CastList").MakeGenericMethod(_messageItemMap[level1Code]);
var theMessageItemModel = castListMethod.Invoke(null, new object[] { messages });

var printMethod = printHelper.GetType().GetMethod("Print").MakeGenericMethod(theMessageItemModel.GetType().GetGenericArguments().Single());
var docRender = printMethod.Invoke(printHelper, new [] { theMessageItemModel }) as IDocumentRender;
file = docRender.Save(exportFormat);

and 12 properties are found with TypeDescriptor.GetProperties(typeof(T)) and right at the end I get my expected output.

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