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

本文关键字:类型 IInterface 属于 是否 转换 检查 我的 对象 如果 支持 接口 | 更新日期: 2023-09-27 18:10:51

在 C# 中,我有一个使用 generics 传入T的函数,我想运行检查以查看T是否是实现interfaceobject,如果是,则调用该interface上的methods之一。

我不想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"的隐式引用转换。

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

缺少的部分是Cast<>()

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

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

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

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

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

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

例如,假设想要一个方法Zap它接受一个泛型ref参数,如果它实现了IDisposable,则调用Dispose,并将其清除。 如果参数是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的后续调用将使用缓存的委托。 尽管反射可能有点慢,但它只需要为任何类型的T使用一次。 所有后续对具有相同类型T MaybeDisposer.Zap<T>(ref T it)的调用都将直接通过委托调度,从而快速执行。

请注意,如果给定的泛型

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

我能想到的最简单的形式是这样的:

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 重新分配的,所以您仍然需要接口可分配检查。

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));
    }
}

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

在我的回答中,我只是删除了通用<TResult>.我假设这个FilterMe总是关于 T 类型的实体(因为它在同一个 Model 类中(。这解决了TTResult之间的铸造问题。 TResult不必标记为IFilterable,因为不使用IFilterable的任何成员。既然代码已经检查了T是否IFilterable为什么要再次检查(尤其是当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() 转换实体。

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();
}

不必使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>();
        }
    }