In C# how can i check if T is of type IInterface and cast to that if my object supports that interface?

前端 未结 8 1342
粉色の甜心
粉色の甜心 2021-02-05 10:52

In C#, I have a function that passes in T using generics and I want to run a check to see if T is an object that implements a

相关标签:
8条回答
  • 2021-02-05 11:26

    If you have a generic-type parameter that may or may not implement IFoo, it's possible to as cast it to a storage location of type IFoo; if you do that, you may pass it to any method which expects an IFoo, as well as any method that expects a generic parameter constrained to IFoo, but you will lose all generic type information if you do that--the parameter will be passed as type IFoo. Among other things, this will mean that if your original object was a structure, it will be boxed.

    If you wish to test whether a generic parameter type implements IFoo and call a method that takes a generic-constrained IFoo if it does, while keeping the original generic type (this could be useful if the type is a struct, and could be necessary if the type is being passed to a generic method which has both IFoo and IBar constraints and the things one might want to pass do not share any single common supertype), it will be necessary to use Reflection.

    For example, suppose one wants to have a method Zap which takes a generic ref parameter, calls Dispose on it if it implements IDisposable, and clears it out. If the parameter is an IDisposable class type, a null test should be performed as an atomic operation with clearing out the parameter.

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

    The first time the code is invoked with any type T, it will determine what sort of generic static class can be produced for that parameter and use Reflection to create a delegate to call a static method of that generic class. Subsequent calls with the same type T will use the cached delegate. Even though Reflection can be a little slow, it will only need to be used once for any type T. All subsequent calls to MaybeDisposer.Zap<T>(ref T it) with the same type T will be dispatched directly through the delegate and will thus execute quickly.

    Note that the calls to MakeGenericType will throw exceptions if they are given generic type parameters which do not meet the constraints of the given open generic classes (e.g. if T was a class or did not implement IDisposable, an attempt to make the generic type StructDisposer<T> would throw an exception); such tests occur at run-time and are not validated by the compiler, so you can use run-time checking to see if the types meet appropriate constraints. Note that the code does not test whether it implements IDisposable, but instead tests whether T does. This is very important. Otherwise, if MaybeDispose was called with a parameter of type Object which held a reference to a Stream, it would determine that it implemented IDisposable and thus try to create a ClassDisposer<Object>, crashing because Object does not implement IDisposable.

    0 讨论(0)
  • 2021-02-05 11:30

    Is the OfType(...) method (link) what you're looking for?

    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));
        }
    }
    
    0 讨论(0)
  • 2021-02-05 11:37

    In my answer, I assume that method FilterMe is used internally and should not be visible outside your model and could be marked private. If my assumption is wrong, you could create a private overload of FilterMe.

    In my answer I just removed the generic <TResult>. I assume that this FilterMe always is about then entities of type T (since it is in the same Model class). This solves the problem about casting between T and TResult. TResult does not have to be marked as IFilterable since none of the members of IFilterable are used. And since the code already checks if T is IFilterable why check again (especially when FilterMe would be private)?

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

    If you would create a second FilterMe, replace the IEumerable<T> types with Queryable<T>, so you do not have to convert your entities with AsQueryable().

    0 讨论(0)
  • 2021-02-05 11:44

    The simplest form I can come up with is something like:

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

    The point here is the need to have types to pass into and get back out of the method. I had to change the types locally to get it working.

    OfType will silently filter out items that are not really of a given type, so it assumes that it's a collection of the same type in any one call.

    Because you are re-assigning from FilterMe, you still need the interface assignable check.

    0 讨论(0)
  • 2021-02-05 11:45

    The missing piece is Cast<>():

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

    Note the use of Cast<>() to convert the entities list to the correct subtype. This cast would fail unless T implements IFilterable, but since we already checked that, we know that it will.

    0 讨论(0)
  • 2021-02-05 11:46

    You don't have to make the FilterMe method a generic method to achieve the same result.

        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>();
            }
        }
    
    0 讨论(0)
提交回复
热议问题