通过外部功能约束通用类型 [英] Constraining generic types by extrinsic functionality

查看:60
本文介绍了通过外部功能约束通用类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:

我正在与一个组织合作,该组织具有不断增长的数据类型集合,他们需要从中提取数据。我无权更改这些数据类型。有些是由其他组织提供的XML文件由机器生成的;有些是由不动摇的内部开发团队控制的;有些太旧了,以至于没人担心会破坏整个地球的稳定并使其坠入太阳,因此没人愿意以任何方式改变它们。这些类不共享任何公共接口,并且不衍生自 object 以外的任何公共类型。下面给出了一些示例类以供说明:

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

我有创建了一个抽象的通用 Extractor< T> 类以用作通用方法的存储库,以及一个 IExtractor< T> 接口,作为访问功能的便捷句柄。我也有一些此类的特定(泛型?)实现,可以从根据某些数据类型构建的业务对象中提取信息。以下是一些示例代码来说明:

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. 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 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.

我想从一个单一的入口点开始支持大量变化的受支持数据类型,这些入口点动态确定要使用的 IExtractor<> 的正确版本基于传入的动态dataObject 。如果没有实现 IExtractor<> 的类来支持给定的 dataObject ,则应该引发错误。 。

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. If there is no class that implements the IExtractor<> to support the given dataObject, than an error should be thrown.

什么不起作用:

我尝试了动态事物,并使用 typeof(Extractor<>)。MakeGenericType(thing.GetType())创建 Extractor< Untouchable> Extractor< Unchangeable> ,但是它们是抽象类,所以我不能使用 Activator.CreateInstance()来构建这些类的实例。这种方法的核心问题是不幸的是,它正在寻找形式为 Extractor<> class 而不是接口 IExtractor<> 的形式的strong>。

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. 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<>.

我考虑过放置扩展方法,例如 IExtractor T BuildExtractor(这是T的东西),但是我担心遇到其中一种已经存在的名为 BuildExtractor 的业务逻辑不可动摇的班级。这可能是不健康的妄想症,但这就是我的处境。

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. This may be an unhealthy level of paranoia, but that's where I'm at.

需要帮助的地方:

我欢迎任何有关如何为不受约束的类集合创建单个入口点的建议。

I'd welcome any suggestions for how I can create a single entrypoint for an unconstrained collection of classes. Thanks in advance.

推荐答案

结合StackOverflow的一些代码和我自己的测试,我建议使用Reflection查找实现接口的所有类型。 :

Combining some code from StackOverflow and my own testing, I suggest using Reflection to find all types implementing an interface:

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

这篇关于通过外部功能约束通用类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆