在C#中,如果我的对象支持该接口,我如何检查T是否为Interface类型并转换为该类型?

时间:2013-04-25 03:26:41

标签: c# generics interface

在C#中,我有一个使用T传入generics的函数,我想运行检查以查看T是否为实现{object的{​​{1}} {1}}如果是,请拨打interface上的methods之一。

我不希望interface约束只有该类型。有可能这样做吗?

例如:

T

我得到的错误是:

错误21类型'TResult'不能在泛型类型或方法'FilterMe(System.Collections.Generic.IEnumerable)'中用作类型参数'TResult'。没有从'TResult'到'IFilterable'的隐式引用转换。

8 个答案:

答案 0 :(得分:18)

缺少的部分是Cast<>()

if(typeof(IFilterable).IsAssignableFrom(typeof(T))) {
    entities = FilterMe(entities.Cast<IFilterable>()).AsQueryable().Cast<T>();
}

请注意使用Cast<>()将实体列表转换为正确的子类型。除非T实现IFilterable,否则此强制转换将失败,但由于我们已经检查过,我们知道它会。

答案 1 :(得分:16)

if (typeof(IMyInterface).IsAssignableFrom(typeof(T))

检查是否可以从IMyInterface类型的实例分配T类型的变量。

答案 2 :(得分:3)

如果您的通用类型参数可能会或可能不会实现IFoo,则可以as将其转换为IFoo类型的存储位置;如果你这样做,你可以将它传递给任何期望IFoo的方法,以及任何期望通用参数约束到IFoo的方法,但如果你这样做,你将失去所有通用类型信息 - 参数将作为类型IFoo传递。除此之外,这意味着如果你的原始对象是一个结构,它将被装箱。

如果您希望测试泛型参数类型是否实现IFoo并调用一个采用泛型约束IFoo的方法(如果有),同时保留原始泛型类型(如果类型是结构,如果将类型传递给同时具有IFooIBar约束的泛型方法,并且可能想要传递的内容不共享任何单个常见超类型,则可能是必需的),有必要使用反射。

例如,假设有人希望方法Zap采用通用ref参数,如果它实现Dispose则调用IDisposable,并清除它。如果参数是IDisposable类类型,则应将null测试作为原子操作执行,并清除参数。

public static class MaybeDisposer
{
    static class ClassDisposer<T> where T : class,IDisposable
    {
        public static void Zap(ref T it)
        {
            T old_it = System.Threading.Interlocked.Exchange(ref it, null);
            if (old_it != null)
            {
                Console.WriteLine("Disposing class {0}", typeof(T));
                old_it.Dispose();
            }
            else
                Console.WriteLine("Class ref {0} already null", typeof(T));
        }
    }
    static class StructDisposer<T> where T : struct,IDisposable
    {
        public static void Zap(ref T it)
        { 
            Console.WriteLine("Disposing struct {0}", typeof(T));
            it.Dispose();
            it = default(T);
        }
    }
    static class nonDisposer<T>
    {
        public static void Zap(ref T it)
        {
            Console.WriteLine("Type {0} is not disposable", typeof(T));
            it = default(T);
        }
    }
    class findDisposer<T>
    {
        public static ActByRef<T> Zap = InitZap;
        public static void InitZap(ref T it)
        {
            Type[] types = {typeof(T)};
            Type t;
            if (!(typeof(IDisposable).IsAssignableFrom(typeof(T))))
                t = typeof(MaybeDisposer.nonDisposer<>).MakeGenericType(types);
            else if (typeof(T).IsValueType)
                t = typeof(MaybeDisposer.StructDisposer<>).MakeGenericType(types);
            else
                t = typeof(MaybeDisposer.ClassDisposer<>).MakeGenericType(types);
            Console.WriteLine("Assigning disposer {0}", t);
            Zap = (ActByRef<T>)Delegate.CreateDelegate(typeof(ActByRef<T>), t, "Zap");
            Zap(ref it);
        }
    }
    public static void Zap<T>(ref T it)
    {
        findDisposer<T>.Zap(ref it);
    }
}

第一次使用任何类型T调用代码时,它将确定可以为该参数生成哪种通用静态类,并使用Reflection创建委托以调用该泛型类的静态方法。具有相同类型T的后续调用将使用缓存的委托。尽管Reflection可能有点慢,但只需要对任何类型T使用一次。所有后续对具有相同类型MaybeDisposer.Zap<T>(ref T it)的{​​{1}}的调用将直接通过代理调度,因此将很快执行。

请注意,对T的调用将抛出异常,如果它们被赋予了不符合给定开放泛型类的约束的泛型类型参数(例如,如果MakeGenericType是一个类或者没有实现T,尝试使泛型类型IDisposable会抛出异常);此类测试在运行时发生,并且未由编译器验证,因此您可以使用运行时检查来查看类型是否满足适当的约束。请注意,代码不会测试StructDisposer<T>是否实现it,而是测试IDisposable是否有效。这是非常重要的。否则,如果使用T类型的参数调用MaybeDispose,该参数保留对Object的引用,则会确定Stream已实施it,从而尝试创建IDisposable,崩溃,因为ClassDisposer<Object>未实现Object

答案 3 :(得分:2)

我能提出的最简单的形式是:

public IEnumerable<T> GetRecords()
{
    IQueryable<T> entities = new List<T>().AsQueryable();

    if (typeof(IFilterable).IsAssignableFrom(typeof(T)))
    {
        entities = FilterMe<IFilterable, T>(entities.OfType<IFilterable>()).AsQueryable();
    }

    return entities;
}

public IEnumerable<TResult> FilterMe<TSource, TResult>(IEnumerable<TSource> linked) where TSource : IFilterable
{
    return linked.Where(r => true).OfType<TResult>();
}

这里的要点是需要让类型传入和退出方法。我不得不在本地更改类型以使其正常工作。

OfType将静默过滤掉实际上不属于给定类型的项目,因此它假定它是任何一个调用中相同类型的集合。

因为您要从FilterMe重新分配,所以仍需要接口可分配检查。

答案 4 :(得分:1)

OfType(...)方法(link)是您要找的?

public class MyModel<T> : IModel<T> where T : MyObjectBase
{

    public IQueryable<T> GetRecords()
    {
        var entities = Repository.Query<T>();
        if (typeof(IFilterable).IsAssignableFrom(typeof(T)))
        {
            //Filterme is a method that takes in IEnumerable<IFilterable>
            entities = FilterMe(entities));
        }
        return entities;
    }

     public IEnumerable<TResult> FilterMe<TResult>(IEnumerable<TResult> linked) where TResult :  IFilterable
    {
        var dict = GetDict();
        return linked.OfType<TResult>().Where(r => dict.ContainsKey(r.Id));
    }
}

答案 5 :(得分:1)

在我的回答中,我假设方法FilterMe在内部使用,不应在模型外可见,并且可以标记为private。如果我的假设是错误的,您可以创建FilterMe的私有重载。

在我的回答中,我刚删除了通用<TResult>。我假设这个FilterMe始终是T类型的实体(因为它在同一个Model类中)。这解决了TTResult之间的投射问题。由于TResult的所有成员均未使用,因此IFilterable不必标记为IFilterable。由于代码已经检查TIFilterable,为什么再次检查(特别是当FilterMe是私有的时候?)

    public IQueryable<T> GetRecords()
    {
        var entities = Repository.Query<T>();
        if (typeof(IFilterable).IsAssignableFrom(typeof(T)))
        {
            //Filterme is a method that takes in IEnumerable<IFilterable>
            entities = FilterMe(entities).AsQueryable();
        }
        return entities;
    }

    public IEnumerable<T> FilterMe(IEnumerable<T> linked) 
    {
        var dict = GetDict();
        return linked.Where(r => dict.ContainsKey(r.Id));
    }

如果您要创建第二个FilterMe,请将IEumerable<T>类型替换为Queryable<T>,这样您就不必使用AsQueryable()转换实体。

答案 6 :(得分:1)

public IEnumerable<TResult> FilterMe<TResult>(IEnumerable<TResult> linked) where TResult : IFilterable
{
    var dict = GetDict();
    return linked.Where(r => dict.ContainsKey(r.Id));
}

尝试使用此版本替换FilterMe:

public IEnumerable<T> FilterMe(IEnumerable<IFilterable> linked)
{
    var dict = GetDict();
    return linked.Where(r => dict.ContainsKey(r.Id)).Cast<T>();
}

然后,你打电话,把你的代码更改为:

if (typeof(IFilterable).IsAssignableFrom(typeof(T)))
{
    //Filterme is a method that takes in IEnumerable<IFilterable>
    var filterable = entities.Cast<IFilterable>();
    entities = FilterMe(entities).AsQueryable();
}

答案 7 :(得分:1)

您不必将FilterMe方法设为通用方法即可获得相同的结果。

    public class MyModel<T> : IModel<T> where T : MyObjectBase {
        public IQueryable<T> GetRecords()
    {
        var entities = Repository.Query<T>();
        if (typeof(IFilterable).IsAssignableFrom(typeof(T)))
        {
            //Filterme is a method that takes in IEnumerable<IFilterable>
            entities = FilterMe(entities.Cast<IFilterable>());
        }
        return entities;
    }

        public IEnumerable<T> FilterMe(IEnumerable<IFilterable> linked)  {
            var dict = GetDict();
            return linked
                    .Where(r => dict.ContainsKey(r.Id))
                    .Cast<T>();
        }
    }