Babu Mannavalappil
Babu Mannavalappil

Reputation: 577

Using a Type variable in generic collection

I have a WebAPI method and in it I have an IEnumerable<T> of objects where the type of T is not known until runtime. So, I need to execute a foreach on the IEnumerable<T> collection. The problem is I cannot replace the T with a specific type since that type is not known until runtime. So, I get the type of T using some reflection in a helper method and store it into a variable "type". But apparently, I cannot use IEnumerable<type>. It throws an error saying a variable cannot be used as a type.

My WebAPI method needs to log activity whenever an update happens to certain types of data. So, I have a custom ActionFilter attribute setup to do the dirty work.

Here is my WebAPI method (this is just a dummy test method):

[HttpPost]
[Route("testlogging")]
[PhiLog]
public IEnumerable<Person> UpdateTestData(Person person)
{
    IList<Person> persons = new List<Person>();
    persons.Add( person );
    persons.Add( person );
    return persons;
}

When OnActionExecuted is executed, we don't know the type of response content returned to the caller. It could be just null, or just a string, or just an int or IList<T> or IEnumerable<T> and the type of T is not known. Here is my Custom Attribute:

public class PhiLogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted( HttpActionExecutedContext actionExecutedContext )
    {
        var user = AuthenticationHelper.GetCurrentUserFromRequest( actionExecutedContext.Request );
        var actionDescriptor = actionExecutedContext.Request.GetActionDescriptor();
        var controllerName = actionDescriptor.ControllerDescriptor.ControllerName;
        var methodName = actionDescriptor.ActionName;
        var action = string.Format( "{0}/{1}", controllerName, methodName );

        var responsePayload = actionExecutedContext.Response.Content as ObjectContent;
        var payloadType = responsePayload.ObjectType;

        /* The variable below (payloadObj) could be of any type - an int, a
           string or a generic ICollection.  The code below is just assuming
           it is of type IList<Person>.  This is for my test purpose */
        var payloadObj = responsePayload.Value;

        /* AppHelper.GetEnumerableType returns the type of the generic
           parameter T. In this case, a Person type */
        var type = AppHelper.GetEnumerableType( payloadObj.GetType());

        if ( payloadType.GetInterfaces().Any( x => x.GetType().Name == "ICollection`1") )
        {
            /* This is where I am stumped.  I need to walk this
               ICollection<T> and log some value from few properties of T.
               At runtime, payloadObj is of type object.  I need to cast it
               into the correct ICollection type */
            foreach (var x in (ICollection<type>)payloadObj)
            {
                //do something with var x here.

                /* But ICollection<type> throws an error "type is a variable
                   but used like a type" */
            }
        }
    }
}

Here is my helper method to get the type of the type parameter T.

public static Type GetEnumerableType( Type type )
{
    var interfaceTypes = type.GetInterfaces();

    foreach ( Type interfaceType in interfaceTypes )
    {
        if ( interfaceType.IsGenericType && interfaceType.Name == "ICollection`1" )
            return interfaceType.GetGenericArguments()[ 0 ];
    }
    return null;
}

I have commented inline in the code where the problem is. Anyone please tell me how can I use a variable in place of T in an IEnumerable<T>. Thanks.

Upvotes: 3

Views: 1261

Answers (1)

Michael Liu
Michael Liu

Reputation: 55529

How can I use a variable in place of T in an IEnumerable<T>?

You can't. Generic type arguments must be known at compile time.

Fortunately, in your particular scenario, there's a simple alternative. Because the ICollection<T> interface inherits the non-generic IEnumerable interface, you can just cast your object to IEnumerable and enumerate that:

// using System.Collections;

foreach (object x in (IEnumerable)payloadObj)
{
    // Access properties of x using reflection or by casting to a known type...
}

Upvotes: 2

Related Questions