【问题标题】:Using a Type variable in generic collection在泛型集合中使用类型变量
【发布时间】:2017-01-20 19:10:38
【问题描述】:

我有一个 WebAPI 方法,其中有一个 IEnumerable<T> 对象,其中 T 的类型直到运行时才知道。所以,我需要在IEnumerable<T> 集合上执行foreach。问题是我无法将 T 替换为特定类型,因为该类型直到运行时才知道。因此,我使用辅助方法中的一些反射来获取 T 的类型,并将其存储到变量“类型”中。但显然,我不能使用IEnumerable<type>。它会抛出一个错误,指出变量不能用作类型。

每当某些类型的数据发生更新时,我的 WebAPI 方法都需要记录活动。所以,我有一个自定义的 ActionFilter 属性设置来完成这项肮脏的工作。

这是我的 WebAPI 方法(这只是一个虚拟测试方法):

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

执行 OnActionExecuted 时,我们不知道返回给调用者的响应内容的类型。它可能只是 null,或者只是一个字符串,或者只是一个 int 或 IList&lt;T&gt;IEnumerable&lt;T&gt;,并且 T 的类型未知。这是我的自定义属性:

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" */
            }
        }
    }
}

这是我获取类型参数 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;
}

我已经在问题所在的代码中注释了内联。任何人请告诉我如何在IEnumerable&lt;T&gt; 中使用变量代替 T。谢谢。

【问题讨论】:

    标签: c# linq generics asp.net-web-api reflection


    【解决方案1】:

    如何在IEnumerable&lt;T&gt; 中使用变量代替 T?

    你不能。泛型类型参数必须在编译时知道。

    幸运的是,在您的特定场景中,有一个简单的替代方案。因为ICollection&lt;T&gt; 接口继承了-泛型IEnumerable 接口,所以您只需将对象转换为IEnumerable 并枚举:

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

    【讨论】:

    • 而上面的类型检查可以换成if (payloadObj is IEnumerable)
    • @juunas:这可能会改变代码的行为。原始代码专门检查ICollection&lt;T&gt;。一些类(如 System.String)实现了IEnumerable,但没有实现ICollection&lt;T&gt;
    • @Michael:谢谢迈克尔。我会尝试你的建议并告诉你。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多