简体   繁体   English

通过外部功能限制泛型类型

[英]Constraining generic types by extrinsic functionality

Background:背景:

I am working with an organization that has an ever-growing collection of data types that they need to extract data from.我正在与一个组织合作,该组织拥有不断增长的数据类型集合,他们需要从中提取数据。 I have no ability to change these data types.我无法更改这些数据类型。 Some are machine-generated from XML files provided by other organizations;有些是由其他组织提供的 XML 文件机器生成的; some are controlled by intransigent internal dev teams;有些由顽固的内部开发团队控制; and some are so old that no one is willing to change them in any way for fear that it will destabilize the entire Earth and cause it to crash into the sun.有些太老了,没有人愿意以任何方式改变它们,因为担心它会破坏整个地球的稳定并导致它撞向太阳。 These classes don't share any common interface, and don't derive from any common type other than object .这些类不共享任何公共接口,也不从除object之外的任何公共类型派生。 A few sample classes are given below for illustration purposes:为了说明目的,下面给出了一些示例类:

    public class Untouchable
    {
        public string Data;
    }

    public class Unchangeable
    {
        public int Info;
    }

The good news is that most of the time, I can use canned functionality to get at least some of the data from instances of the various classes.好消息是,在大多数情况下,我可以使用固定功能从各种类的实例中获取至少一些数据。 Unfortunately, most of these classes also have weird and specialized data that needs class-specific logic to extract data from.不幸的是,这些类中的大多数也有奇怪的和专门的数据,需要类特定的逻辑来从中提取数据。 Also, information often needs to persist inside of the data extractors because the data objects I'm pulling data from have "interactions" (don't ask).此外,信息通常需要保留在数据提取器内部,因为我从中提取数据的数据对象具有“交互”(不要问)。

I have created an abstract generic Extractor<T> class to serve as a repository of common methodology, and an IExtractor<T> interface to serve as a convenient handle to access functionality.我创建了一个抽象的通用Extractor<T>类作为通用方法的存储库,并创建了一个IExtractor<T>接口作为访问功能的便捷句柄。 I also have a few specific (de-generic?) implementations of this class that can extract information from the business objects built from some of the data types.我还有一些此类的特定(非通用?)实现,它们可以从根据某些数据类型构建的业务对象中提取信息。 Here's some sample code to illustrate:下面是一些示例代码来说明:

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

I don't yet support all of the available data types, but management wants to roll out the data extractor to end-users using a command-line interface.我还不支持所有可用的数据类型,但管理层希望使用命令行界面向最终用户推出数据提取器。 They're telling me that we should start extracting the data we can extract, and get to the rest later.他们告诉我,我们应该开始提取我们可以提取的数据,然后再处理其余的。 Support for the many unmodifiable types will be added by me and by and other programmers as time permits, and the end-users are expected to work around our latency.在时间允许的情况下,我和其他程序员将添加对许多不可修改类型的支持,最终用户应该解决我们的延迟问题。 This actually makes sense in our real-world setting, so just go with it.这在我们的现实世界中实际上是有意义的,所以就随它去吧。

The Problem:问题:

The list of data types that we want to pull information from is very large.我们要从中提取信息的数据类型列表非常大。 Maintaining an explicit list of supported types in code will be tricky and error prone -- especially if we find any problems with specific data extractors and need to revoke support until we fix some bugs.在代码中维护支持类型的明确列表将是棘手且容易出错的——特别是如果我们发现特定数据提取器有任何问题并且需要撤销支持直到我们修复一些错误。

I would like to support the large and changing list of supported data types from a single entry point that dynamically determines the "right version" of IExtractor<> to use based on a passed in dynamic dataObject .我想从单个入口点支持大量且不断变化的受支持数据类型列表,该入口点根据传入的dynamic dataObject动态确定要使用的IExtractor<>的“正确版本”。 If there is no class that implements the IExtractor<> to support the given dataObject , than an error should be thrown.如果没有实现IExtractor<>以支持给定dataObject ,则应抛出错误。

What Doesn't Work:什么不起作用:

I tried taking a dynamic thing and using typeof(Extractor<>).MakeGenericType(thing.GetType()) to create an instance of Extractor<Untouchable> or Extractor<Unchangeable> , but those are abstract classes, so I can't use Activator.CreateInstance() to build an instance of those classes.我尝试采用dynamic thing并使用typeof(Extractor<>).MakeGenericType(thing.GetType())创建Extractor<Untouchable>Extractor<Unchangeable>的实例,但这些是抽象类,所以我不能使用Activator.CreateInstance()来构建这些类的实例。 The core issue with this approach is that it's unfortunately looking for a class of the form Extractor<> instead of an interface of the form IExtractor<> .这种方法的核心问题是不幸的是它正在寻找一个Extractor<>形式的,而不是IExtractor<>形式的接口

I considered putting extension methods like IExtractor<T> BuildExtractor(this T something) in some class, but I'm nervous about running into some business logic called BuildExtractor that already exists in one of these untouchable classes.我考虑在某个类中放置像IExtractor<T> BuildExtractor(this T something)扩展方法,但我很担心BuildExtractor一些名为BuildExtractor业务逻辑,这些逻辑已经存在于这些不可触及的类之一中。 This may be an unhealthy level of paranoia, but that's where I'm at.这可能是一种不健康的偏执水平,但这就是我所处的位置。

Where I need help:我需要帮助的地方:

I'd welcome any suggestions for how I can create a single entrypoint for an unconstrained collection of classes.我欢迎任何有关如何为不受约束的类集合创建单个入口点的建议。 Thanks in advance.提前致谢。

The following snippet will create the concrete instance of Extractor<T> class and dynamically invokes a method of this instance以下代码段将创建Extractor<T>类的具体实例并动态调用此实例的方法

var test = new Unchangeable();
var baseType = typeof(Extractor<>).MakeGenericType(test.GetType());
var extractorType = Assembly.GetExecutingAssembly()
    .GetTypes().FirstOrDefault(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(baseType));
if (extractorType != null)
{
    dynamic extractor = Activator.CreateInstance(extractorType);
    string result = extractor?.ExtractionTwo(test);
}

Of course, it's simplified, you can pass a specific instance of Unchangeable or Untouchable class and restrict assembly types scanning (and get all types only once).当然,它是简化的,您可以传递UnchangeableUntouchable类的特定实例并限制程序集类型扫描(并且只获取一次所有类型)。

The disadvantage here is that you have to pay attention to ExtractionOne and ExtractionTwo signatures, since they are invoked on dynamic object这里的缺点是您必须注意ExtractionOneExtractionTwo签名,因为它们是在动态对象上调用的

The core issue with this approach is that it's unfortunately looking for a class of the form Extractor<> instead of an interface of the form IExtractor<> .这种方法的核心问题是不幸的是它正在寻找一个Extractor<>形式的类,而不是IExtractor<>形式的接口。

This snippet can help you to look through types using IExtrator<> interface此代码段可以帮助您使用IExtrator<>接口查看类型

var baseType = typeof(IExtractor<>).MakeGenericType(typeof(Unchangeable));
var extractorType = Assembly.GetExecutingAssembly()
    .GetTypes().FirstOrDefault(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t));

Combining some code from StackOverflow and my own testing, I suggest using Reflection to find all types implementing an interface:结合 StackOverflow 的一些代码和我自己的测试,我建议使用反射来查找实现接口的所有类型:

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

}

Reflection can be quite slow, and in my testing, getting all loaded types was the slowest part, so I added caching of the loaded types and the implementing types, but this does mean you will need to refresh the loaded types if you dynamically load assemblies:反射可能很慢,在我的测试中,获取所有加载的类型是最慢的部分,所以我添加了加载类型和实现类型的缓存,但这确实意味着如果你动态加载程序集,你将需要刷新加载的类型:

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

Once you have one of these, you can make a factory method that takes a dynamic object:一旦你有了其中一个,你就可以创建一个接受动态对象的工厂方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM