在C#中,我有一个使用T
传入generics
的函数,我想运行检查以查看T
是否为实现{object
的{{1}} {1}}如果是,请拨打interface
上的methods
之一。
我不希望interface
约束只有该类型。有可能这样做吗?
例如:
T
我得到的错误是:
错误21类型'TResult'不能在泛型类型或方法'FilterMe(System.Collections.Generic.IEnumerable)'中用作类型参数'TResult'。没有从'TResult'到'IFilterable'的隐式引用转换。
答案 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
的方法(如果有),同时保留原始泛型类型(如果类型是结构,如果将类型传递给同时具有IFoo
和IBar
约束的泛型方法,并且可能想要传递的内容不共享任何单个常见超类型,则可能是必需的),有必要使用反射。
例如,假设有人希望方法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类中)。这解决了T
和TResult
之间的投射问题。由于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()
转换实体。
答案 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>();
}
}