C# 通过外部功能约束泛型类型
背景: 我与一家组织合作,该组织拥有不断增长的数据类型集合,他们需要从中提取数据。我无法更改这些数据类型。有些是由其他组织提供的XML文件机器生成的;一些由不妥协的内部开发团队控制;还有一些是如此古老,以至于没有人愿意以任何方式改变它们,因为害怕它会破坏整个地球的稳定,导致它撞向太阳。这些类不共享任何公共接口,也不派生自除C# 通过外部功能约束泛型类型,c#,.net,generics,dynamic,interface,C#,.net,Generics,Dynamic,Interface,背景: 我与一家组织合作,该组织拥有不断增长的数据类型集合,他们需要从中提取数据。我无法更改这些数据类型。有些是由其他组织提供的XML文件机器生成的;一些由不妥协的内部开发团队控制;还有一些是如此古老,以至于没有人愿意以任何方式改变它们,因为害怕它会破坏整个地球的稳定,导致它撞向太阳。这些类不共享任何公共接口,也不派生自除对象以外的任何公共类型。以下给出了几个示例类以供说明: public class Untouchable { public string Dat
对象
以外的任何公共类型。以下给出了几个示例类以供说明:
public class Untouchable
{
public string Data;
}
public class Unchangeable
{
public int Info;
}
好消息是,在大多数情况下,我可以使用固定功能从各种类的实例中获取至少一些数据。不幸的是,这些类中的大多数也有奇怪的专门数据,需要特定于类的逻辑来从中提取数据。此外,信息通常需要保存在数据提取器内部,因为我从中提取数据的数据对象具有“交互”(不要问)
我创建了一个抽象的泛型提取器
类作为通用方法的存储库,以及一个IExtractor
接口作为访问功能的方便句柄。我还有一些此类的特定(非泛型?)实现,它们可以从一些数据类型构建的业务对象中提取信息。下面是一些示例代码来说明:
public interface IExtractor<T>
{
string ExtractionOne(T something);
string ExtractionTwo(T something);
}
public abstract class Extractor<T> : IExtractor<T>
{
private string internalInfo; // Certain business logic requires us to keep internal info over multiple objects that we extract data from.
protected Extractor() { internalInfo="stuff"; }
public virtual string ExtractionOne(T something)
{
return "This manipulation is generally applicable to most conceivable types.";
}
public abstract string ExtractionTwo(T something); // This DEFINITELY needs to be overridden for each class T
}
public class UntouchableExtractor : Extractor<Untouchable>
{
public UntouchableExtractor() : base() { }
public override string ExtractionTwo(Untouchable something)
{
return something.Data;
}
}
public class UnchangeableExtractor : Extractor<Unchangeable>
{
public UnchangeableExtractor() : base() { }
public override string ExtractionTwo(Unchangeable something)
{
return something.Info.ToString();
}
}
公共接口IExtractor
{
弦提取酮(T某物);
字符串提取2(T某物);
}
公共抽象类提取器:IExtractor
{
私有字符串internalInfo;//某些业务逻辑要求我们在从中提取数据的多个对象上保留内部信息。
受保护的提取器(){internalInfo=“stuff”;}
公共虚拟字符串提取ONE(T)
{
return“此操作通常适用于大多数可能的类型。”;
}
公共抽象字符串ExtractionTwo(T something);//这肯定需要为每个类T重写
}
公共类非接触提取器:提取器
{
公共非接触提取器():base(){}
公共重写字符串提取2(不可触及的内容)
{
返回某物。数据;
}
}
公共类不可更改提取器:提取器
{
公共不可更改提取器():base(){}
公共重写字符串提取2(不可更改的内容)
{
返回something.Info.ToString();
}
}
我还不支持所有可用的数据类型,但管理层希望使用命令行界面向最终用户推出数据提取器。他们告诉我,我们应该开始提取我们可以提取的数据,然后再进行其他工作。在时间允许的情况下,我和其他程序员将添加对许多不可修改类型的支持,最终用户有望解决我们的延迟问题。这在我们的现实环境中是有意义的,所以就这么做吧
问题:
我们要从中提取信息的数据类型列表非常大。在代码中维护受支持类型的显式列表将是一件棘手且容易出错的事情——特别是如果我们发现特定数据提取器存在任何问题,并且需要撤销支持,直到我们修复了一些bug
我想从一个入口点支持大量不断变化的受支持数据类型列表,该入口点根据传入的动态数据对象
动态确定要使用的IExtractor
的“正确版本”。如果没有实现IExtractor
以支持给定dataObject
的类,则应抛出错误
什么不起作用:
我尝试使用动态对象
并使用类型(提取器)。MakeGenericType(thing.GetType())
创建提取器
或提取器
的实例,但这些都是抽象类,因此我无法使用Activator.CreateInstance()
来构建这些类的实例。这种方法的核心问题是,不幸的是,它正在寻找表单提取器的类,而不是表单IExtractor的接口
我曾考虑在某个类中使用扩展方法,如IExtractor BuildExtractor(this T something)
,但我担心遇到一些称为BuildExtractor
的业务逻辑,这些逻辑已经存在于这些不可接触的类中。这可能是一种不健康的偏执狂,但这正是我所处的位置
我需要帮助的地方:
对于如何为不受约束的类集合创建单个入口点,我欢迎任何建议。提前感谢。下面的代码片段将创建提取器
类的具体实例,并动态调用该实例的方法
var测试=新的不可更改();
var baseType=typeof(提取器).MakeGenericType(test.GetType());
var extractorType=Assembly.getExecutionGassembly()
.GetTypes().FirstOrDefault(t=>t.IsClass&!t.isasStract&&t.isubClassof(baseType));
if(提取器类型!=null)
{
动态提取器=Activator.CreateInstance(提取器类型);
字符串结果=提取器?提取器2(测试);
}
当然,它是简化的,您可以传递Unchangeable
或Untouchable
类的特定实例,并限制程序集类型扫描(并且只获取一次所有类型)
这里的缺点是您必须注意ExtractionOne
和ExtractionTwo
签名,因为它们是在动态系统上调用的
public static class TypeExt {
public static bool IsBuiltin(this Type aType) => new[] { "/dotnet/shared/microsoft", "/windows/microsoft.net" }.Any(p => aType.Assembly.CodeBase.ToLowerInvariant().Contains(p));
public static IEnumerable<Type> ImplementingTypes(this Type interfaceType, bool includeAbstractClasses = false, bool includeStructs = false, bool includeSystemTypes = false, bool includeInterfaces = false) =>
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetLoadableTypes())
.Distinct()
.Where(aType => (includeAbstractClasses || !aType.IsAbstract) &&
(includeInterfaces ? aType != interfaceType : !aType.IsInterface) &&
(includeStructs || !aType.IsValueType) &&
(includeSystemTypes || !aType.IsBuiltin()) &&
interfaceType.IsAssignableFrom(aType) &&
aType.GetInterfaces().Contains(interfaceType));
}
public static class AssemblyExt {
//https://stackoverflow.com/a/29379834/2557128
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) {
if (assembly == null)
throw new ArgumentNullException("assembly");
try {
return assembly.GetTypes();
} catch (ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}
}
public static class TypeExt {
public static bool IsBuiltin(this Type aType) => new[] { "/dotnet/shared/microsoft", "/windows/microsoft.net" }.Any(p => aType.Assembly.CodeBase.ToLowerInvariant().Contains(p));
static Dictionary<Type, HashSet<Type>> FoundTypes = null;
static List<Type> LoadableTypes = null;
public static void RefreshLoadableTypes() {
LoadableTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetLoadableTypes()).ToList();
FoundTypes = new Dictionary<Type, HashSet<Type>>();
}
public static IEnumerable<Type> ImplementingTypes(this Type interfaceType, bool includeAbstractClasses = false, bool includeStructs = false, bool includeSystemTypes = false, bool includeInterfaces = false) {
if (FoundTypes != null && FoundTypes.TryGetValue(interfaceType, out var ft))
return ft;
else {
if (LoadableTypes == null)
RefreshLoadableTypes();
var ans = LoadableTypes
.Where(aType => (includeAbstractClasses || !aType.IsAbstract) &&
(includeInterfaces ? aType != interfaceType : !aType.IsInterface) &&
(includeStructs || !aType.IsValueType) &&
(includeSystemTypes || !aType.IsBuiltin()) &&
interfaceType.IsAssignableFrom(aType) &&
aType.GetInterfaces().Contains(interfaceType))
.ToHashSet();
FoundTypes[interfaceType] = ans;
return ans;
}
}
}
public static class AssemblyExt {
//https://stackoverflow.com/a/29379834/2557128
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) {
if (assembly == null)
throw new ArgumentNullException("assembly");
try {
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}
}
public static class ImplementingFactory {
public static Type ExtractorType(dynamic anObject) {
Type oType = anObject.GetType();
var iType = typeof(IExtractor<>).MakeGenericType(oType);
var ans = iType.ImplementingTypes().FirstOrDefault();
if (ans == null)
throw new Exception($"Unable to find IExtractor<{oType.Name}>");
else
return ans;
}
}