Reputation:
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
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 if
s, you should be able to do the following:
return ((IEnumerable<GenericAttachment>)attachments).ToList();
Upvotes: 1
Reputation: 334
Take a look at this. This is an example of memory efficient casting a List<Derived>
to a List<Base>
Upvotes: 0
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