19

在 C# 中,我有一个传入Tusing的函数,generics我想运行检查以查看是否Tobject实现 a 的interface,如果是,则调用methodson that中的一个interface

我不想T限制只能属于那种类型。是否有可能做到这一点?

例如:

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.Where(r => dict.ContainsKey(r.Id));
    }
 }

我得到的错误是:

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

4

8 回答 8

26

缺少的部分是Cast<>()

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

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

于 2013-04-25T03:53:45.723 回答
17
if (typeof(IMyInterface).IsAssignableFrom(typeof(T))

IMyInterface这将检查是否可以从 type 的实例分配type 的变量T

于 2013-04-25T03:27:37.097 回答
3

如果您有一个可能实现也可能不实现的泛型类型参数,则IFoo可以将as其强制转换为 type 的存储位置IFoo;如果你这样做,你可以将它传递给任何需要一个 的方法IFoo,以及任何需要一个泛型参数约束到的方法IFoo,但是如果你这样做,你将丢失所有泛型类型信息——参数将作为类型传递IFoo. 除此之外,这意味着如果您的原始对象是一个结构,它将被装箱。

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

例如,假设一个人想要一个方法Zap,它接受一个泛型参数,如果它实现了就ref调用它,然后清除它。如果参数是类类型,则应将空测试作为原子操作执行,并清除参数。DisposeIDisposableIDisposable

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,它将确定可以为该参数生成哪种泛型静态类,并使用反射创建一个委托来调用该泛型类的静态方法。具有相同类型的后续调用T将使用缓存的委托。尽管反射可能有点慢,但对于任何类型,它只需要使用一次TMaybeDisposer.Zap<T>(ref T it)所有具有相同类型的后续调用T将直接通过委托分派,因此将快速执行。

请注意,MakeGenericType如果给定的泛型类型参数不满足给定的开放泛型类的约束,则调用将引发异常(例如,如果T是类或未实现IDisposable,则尝试创建泛型类型StructDisposer<T>将引发异常) ; 此类测试发生在运行时,编译器不会对其进行验证,因此您可以使用运行时检查来查看类型是否满足适当的约束。请注意,代码不测试是否it实现IDisposable,而是测试是否T实现。这个非常重要。否则,如果使用包含对a 的引用MaybeDispose的类型参数调用,它将确定已实现并因此尝试创建一个ObjectStreamitIDisposableClassDisposer<Object>Object,因为没有实现而崩溃IDisposable

于 2013-04-26T15:12:55.850 回答
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,所以您仍然需要接口可分配检查。

于 2013-05-02T15:16:32.683 回答
1

OfType(...)方法(链接)是你要找的吗?

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));
    }
}
于 2013-04-29T20:10:45.770 回答
1

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

在我的回答中,我刚刚删除了通用的<TResult>. 我假设这FilterMe总是关于类型的实体T(因为它在同一个模型类中)。这解决了在T和之间进行转换的问题TResultTResult不必标记为,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()

于 2013-05-01T13:36:47.330 回答
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();
}
于 2013-05-03T20:23:01.810 回答
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>();
        }
    }
于 2013-05-04T18:02:53.290 回答