user6269864
user6269864

Reputation:

Return a List<type> with a dynamically selected type while also casting to that type (C#)

Resolved, see solution at the end of the post.


I have a method that returns a List of attachments. I have three types of attachments, which all extend a class called GenericAttachment:

GenericAttachment
||
==> FormA_Attachment
==> FormB_Attachment
==> FormC_Attachment

I also have different form types, which all extend a class called GenericForm:

GenericForm
||
==> FormA
==> FormB
==> FormC

The method must take a Type argument which is either FormA, FormB or FormC, and return the attachments of the appropriate type.

I've tried this first:

public static List<GenericAttachment> GetAllAttachmentsByFormID<T>(int sqlFormId, Type type) where T : GenericForm
{
        //this returns e.g. FormA_Attachment based on FormA as the input
        Type attachmentType = GetAttachmentTypeByFormType(type);

        //Call a generic function (overload of this one) 
        //that returns all attachments and requires a specific type argument.
        //Meanwhile, .Invoke()'s return type is just an `object`
        var attachments = typeof(AttachmentManager)
                              .GetMethod("GetAllAttachmentsByFormID", new[] { typeof(int) }) // select the correct overload for the method
                              .MakeGenericMethod(attachmentType)
                              .Invoke(new AttachmentManager(), new object[] { sqlFormId });

        return (List<GenericAttachment>)attachments;
}

However, the cast fails at runtime ("failed to cast").

Then I tried a dumber way with if/else statements, but it doesn't compile because "Cannot convert List<FormA_Attachment> to List<GenericAttachment>". Tried both using Convert.ChangeType and a normal cast, as seen below.

It's weird that it doesn't compile because e.g. FormA_Attachment extends GenericAttachment.

        if (attachmentType == typeof(FormA_Attachment))
        {
            return (List<FormA_Attachment>) Convert.ChangeType(attachments, typeof(List<FormA_Attachment>));
        }
        else if (attachmentType == typeof(FormB_Attachment))
        {
            return (List<FormB_Attachment>)attachments;
        }
        else if (attachmentType == typeof(FormC_Attachment))
        {
            return (List<FormC_Attachment>)attachments;
        }
        else
        {
            throw new Exception("Invalid attachment class type.");
        }

How do I cast the attachments into a List<type>, where type is selected dynamically?


Solution:

Thanks to @Mikhail Neofitov, the below code worked.

attachments has the type object because this is what .Invoke() returns.

So I cast it to a specific type first, then convert to less specific type using .OfType<GenericAttachment>().ToList().

        if (attachmentType == typeof(FormA_Attachment))
        {
            return ((List<FormA_Attachment>) Convert.ChangeType(attachments, typeof(List<FormA_Attachment>))).OfType<GenericAttachment>().ToList();
        }
        else if (attachmentType == typeof(FormB_Attachment))
        ... 
        //similar code

Upvotes: 1

Views: 174

Answers (3)

Eren Ers&#246;nmez
Eren Ers&#246;nmez

Reputation: 39095

You can utilize covariance here. List<T> isn't variant, but it implements IEnumerable<out T> which is covariant in T. This means you cannot convert your object to the List<GenericAttachment>, but you can convert it to an IEnumerable<GenericAttachment>. So instead of all those ifs, you should be able to do the following:

return ((IEnumerable<GenericAttachment>)attachments).ToList();

Upvotes: 1

Jury Golubev
Jury Golubev

Reputation: 334

Take a look at this. This is an example of memory efficient casting a List<Derived> to a List<Base>

Upvotes: 0

Mikhail Tulubaev
Mikhail Tulubaev

Reputation: 4281

C# does not allow types covariance, it means that List<string> could not simple convert to List<object>.

In your case you could use LINQ extension method OfType() as following:

return attachments.OfType<GenericAttachment>().ToList();

Just a note, I guess, you could modify your app architecture to pass resulting type of GenericArgument to the generic parameter of GenericForm and define an abstract method for returning resulting attachments in the result type. Also, your generic parameter <T> is useless, you do not use it in the method body.

Upvotes: 4

Related Questions