我可以使用属性,以便我的工厂知道它可以/应该实例化什么,而不破坏“松散耦合”结构吗?规则?

发布于 2024-10-06 23:35:50 字数 105 浏览 2 评论 0原文

我在我的项目中实现了一个工厂,最近有人建议我在类上使用属性,以便工厂可以确定要实例化并传回哪个类。我是开发世界的新手,试图严格遵循松散耦合的规则,我想知道依赖“钩子”(作为属性)是否违背了这一点?

I have implemented a factory in my project and it was recently suggested that I use attributes on my classes so the factory can determine which class to instantiate and pass back. I am new to the world of development and trying to rigidly follow the loosely-coupled rule, I wondering if relying on "hooks" (being the attributes) goes against this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

标点 2024-10-13 23:35:50

装饰工厂的产品类可以使开发变得更加容易,这是我有时会做的事情。例如,当必须基于数据库中存储的唯一标识符创建产品时,这尤其有用。该唯一 ID 和产品类别之间必须存在映射,并且使用属性可以使映射变得非常清晰和可靠。除此之外,它还允许您添加产品类别,而无需更改工厂。

例如,您可以像这样装饰您的类:

[ProductAttribute(1)]
public class MyFirstProduct : IProduct
{
}

[ProductAttribute(2)]
public class MySecondProduct : IProduct
{
}

您可以像这样实现您的工厂:

public class ProductFactory : IProductFactory
{
    private static Dictionary<int, Type> products =
        new Dictionary<int, Type>();

    static ProductFactory()
    {
        // Please note that this query is a bit simplistic. It doesn't
        // handle error reporting.
        var productsWithId =
          from type in 
              Assembly.GetExecutingAssembly().GetTypes()
          where typeof(IProduct).IsAssignableFrom(type)
          where !type.IsAbstract && !type.IsInterface
          let attributes = type.GetCustomAttributes(
            typeof(ProductAttribute), false)
          let attribute = attributes[0] as ProductAttribute
          select new { type, attribute.Id };

        products = productsWithId
            .ToDictionary(p => p.Id, p => p.type);
    }

    public IProduct CreateInstanceById(int id)
    {
        Type productType = products[id];

        return Activator.CreateInstance(productType) as IProduct;
    }
}

完成此操作后,您可以使用该工厂来创建如下产品:

private IProductFactory factory;

public void SellProducts(IEnumerable<int> productIds)
{
    IEnumerable<IProduct> products =
        from productId in productIds
        select factory.CreateInstanceById(productId);

    foreach (var product in products)
    {
        product.Sell();
    }
}

我过去使用过这个概念,例如创建基于发票计算的发票在数据库标识符上。该数据库包含每种发票类型的计算列表。实际计算是在 C# 类中定义的。

Decorate product classes of a factory can make development much easier and it is something I sometime do. This is especially useful when products must be created based on a unique identifier stored in a database for instance. There must be a mapping between that unique id and the product class and using an attribute makes this very clear and reabable. Besides this, it allows you to add product classes, without changing the factory.

For instance, you can decorate your class like this:

[ProductAttribute(1)]
public class MyFirstProduct : IProduct
{
}

[ProductAttribute(2)]
public class MySecondProduct : IProduct
{
}

And you can implement your factory like this:

public class ProductFactory : IProductFactory
{
    private static Dictionary<int, Type> products =
        new Dictionary<int, Type>();

    static ProductFactory()
    {
        // Please note that this query is a bit simplistic. It doesn't
        // handle error reporting.
        var productsWithId =
          from type in 
              Assembly.GetExecutingAssembly().GetTypes()
          where typeof(IProduct).IsAssignableFrom(type)
          where !type.IsAbstract && !type.IsInterface
          let attributes = type.GetCustomAttributes(
            typeof(ProductAttribute), false)
          let attribute = attributes[0] as ProductAttribute
          select new { type, attribute.Id };

        products = productsWithId
            .ToDictionary(p => p.Id, p => p.type);
    }

    public IProduct CreateInstanceById(int id)
    {
        Type productType = products[id];

        return Activator.CreateInstance(productType) as IProduct;
    }
}

After doing this you can use that factory for creating products like this:

private IProductFactory factory;

public void SellProducts(IEnumerable<int> productIds)
{
    IEnumerable<IProduct> products =
        from productId in productIds
        select factory.CreateInstanceById(productId);

    foreach (var product in products)
    {
        product.Sell();
    }
}

I've used this concept in the past for instance to create invoice calculations based on a database identifier. The database contained a list of calculations per type of invoice. The actual calculations were defined in C# classes.

懒的傷心 2024-10-13 23:35:50

我不认为使用属性会增加工厂与其创建的类之间的耦合,事实上,它会减少这里的耦合,因为工厂将在运行时通过属性发现信息。如果有的话,您只需用正在创建的类之间的耦合来交换与属性的耦合。也就是说,我不确定这会给你带来什么。工厂的要点是将创建逻辑本地化在一个地方。通过将其放入属性中,您再次将其分布在代码中,部分地违背了工厂的目的:现在您必须查看工厂属性来了解对象是如何存在的创建的。

当然,我可能误解了你的问题。您可能意味着类使用其属性上的属性来指示哪些属性需要由工厂实例化。在这种情况下,您将替换一些配置驱动的机制来执行依赖项注入。我当然知道这可能有用;让工厂发现对象的依赖关系并在运行时自动创建它们。在这种情况下,您将稍微增加代码的整体耦合度,因为现在属性和工厂之间存在以前不存在的依赖关系。总的来说,尽管您可能会降低代码复杂性,因为您无需为每个类提供特定代码即可提供其特定依赖项或从配置文件中发现它们。

如果您询问使用属性是否是一个好主意,我认为我们可能需要更多信息,但由于您似乎只是询问是否会违反 OO 原则,所以我不这么认为。我没有看到它增加了工厂和正在创建的类之间的耦合,而只是稍微增加了代码的整体耦合。无论如何,工厂本质上比其他类需要更多的耦合。请记住,它是松耦合,而不是非耦合解耦代码不执行任何操作。你需要类之间的关系才能使任何事情发生。

I don't think that using attributes would be increasing the coupling between the factory and the class it creates, in fact, it would decrease the coupling here because the factory would be discovering the information at runtime via the attributes. If anything you're simply trading the coupling between the class being created for the coupling to the attribute. That said, I'm not sure exactly what that buys you. The point of the factory is that you localize the creational logic in a single place. By putting it into attributes you've spread it all over your code again, partially defeating the purpose of the factory: now you have to look at both the factory and the attribute to understand how the object is being created.

Of course, I may be misunderstanding your question. You may mean that a class uses attributes on it's properties to indicate which of the properties need to be instantiated by the factory. In this case, you're replacing some configuration-driven mechanism for doing dependency injection. I can certainly see where that might be useful; having the factory discover an object's dependencies and automatically create them as well at runtime. In this case, you'd be slightly increasing the overall coupling of your code, since there is now a dependency between the attribute and the factory that didn't exist before. Overall, though you might decrease code complexity since you'd be able to do without specific code for each class to supply its particular dependencies or discover them from a configuration file.

If you're asking if using attributes a good idea, I think we probably need more information, but since you seem to be asking only if you're going to violate an OO principle, I don't think so. I don't see it increasing the coupling between the factory and the class being created and only slightly increasing the overall coupling of the code. Factories, by their nature, need more coupling than other classes anyway. Remember, it's loosely-coupled, not uncoupled. Uncoupled code doesn't do anything. You need relationships between classes to make anything happen.

苦笑流年记忆 2024-10-13 23:35:50

这是一个工厂实现,我用它来根据属性值创建具体实例。它还使用参数进行实例化。

class ViewFactory
{
    public static IView GetViewType(string PropertyValue, SomeOtherObject parentControl){
    Assembly assembly = Assembly.GetAssembly(typeof(ViewFactory));
    var types = from type in assembly.GetTypes()
                where Attribute.IsDefined(type,typeof(ViewTypeAttribute))
                select type;

    var objectType = types.Select(p => p).
        Where(t => t.GetCustomAttributes(typeof(ViewTypeAttribute), false)
            .Any(att => ((ViewTypeAttribute)att).name.Equals(PropertyValue)));

    IView myObject = (IView)Activator.CreateInstance(objectType.First(),parentControl);
    return myObject;
}
}

[ViewTypeAttribute("PropertyValue", "1.0")]
class ListboxView : IView
{


public ListboxView(FilterDocumentChoseTypeFieldControll parentControl)
{

}       
public override void CreateChildrens()
{

}

}

Here is a factory implementation that i used to create concrete instances based on a attribute value. It also instantiates with parameters.

class ViewFactory
{
    public static IView GetViewType(string PropertyValue, SomeOtherObject parentControl){
    Assembly assembly = Assembly.GetAssembly(typeof(ViewFactory));
    var types = from type in assembly.GetTypes()
                where Attribute.IsDefined(type,typeof(ViewTypeAttribute))
                select type;

    var objectType = types.Select(p => p).
        Where(t => t.GetCustomAttributes(typeof(ViewTypeAttribute), false)
            .Any(att => ((ViewTypeAttribute)att).name.Equals(PropertyValue)));

    IView myObject = (IView)Activator.CreateInstance(objectType.First(),parentControl);
    return myObject;
}
}

[ViewTypeAttribute("PropertyValue", "1.0")]
class ListboxView : IView
{


public ListboxView(FilterDocumentChoseTypeFieldControll parentControl)
{

}       
public override void CreateChildrens()
{

}

}
听不够的曲调 2024-10-13 23:35:50

如果有人需要使用 System.Reflection.Emit 的版本...

// just paste this into a Console App

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

class Program
{
    static void Main(string[] args)
    {
        // Here's the usage of a "traditional" factory, which returns objects that implement a common interface.
        // This is a great pattern for a lot of different scenarios.
        // The only downside is that you have to update your factory class whenever you add a new class.
        TraditionalFactory.Create("A_ID").DoIt();
        TraditionalFactory.Create("B_ID").DoIt();        
        Console.ReadKey();

        // But what if we make a class that uses reflection to find attributes of classes it can create? Reflection!
        // This works great and now all we have to do is add an attribute to new classes and this thing will just work.
        // (It could also be more generic in its input / output, but I simplified it for this example)
        ReflectionFactory.Create("A_ID").DoIt();
        ReflectionFactory.Create("B_ID").DoIt();
        // Wait, that's great and all, but everyone always says reflection is so slow, and this thing's going to reflect 
        // on every object creation...that's not good right?
        Console.ReadKey();

        // So I created this new factory class which gives the speed of the traditional factory combined with the flexibility 
        // of the reflection-based factory.
        // The reflection done here is only performed once. After that, it is as if the Create() method is using a switch statement        
        Factory<string, IDoSomething>.Create("A_ID").DoIt();
        Factory<string, IDoSomething>.Create("B_ID").DoIt();

        Console.ReadKey();
    }
}

class TraditionalFactory
{
    public static IDoSomething Create(string id)
    {
        switch (id)
        {
            case "A_ID":
                return new A();
            case "B_ID":
                return new B();
            default:
                throw new InvalidOperationException("Invalid factory identifier");
        }
    }
}

class ReflectionFactory
{
    private readonly static Dictionary<string, Type> ReturnableTypes;

    static ReflectionFactory()
    {
        ReturnableTypes = GetReturnableTypes();
    }

    private static Dictionary<string, Type> GetReturnableTypes()
    {
        // get a list of the types that the factory can return
        // criteria for matching types:
        // - must have a parameterless constructor
        // - must have correct factory attribute, with non-null, non-empty value
        // - must have correct BaseType (if OutputType is not generic)
        // - must have matching generic BaseType (if OutputType is generic)

        Dictionary<string, Type> returnableTypes = new Dictionary<string, Type>();

        Type outputType = typeof(IDoSomething);
        Type factoryLabelType = typeof(FactoryAttribute);
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            string assemblyName = assembly.GetName().Name;
            if (!assemblyName.StartsWith("System") &&
                assemblyName != "mscorlib" &&
                !assemblyName.StartsWith("Microsoft"))
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(factoryLabelType, false).Length > 0)
                    {
                        foreach (object label in ((FactoryAttribute)type.GetCustomAttributes(factoryLabelType, true)[0]).Labels)
                        {
                            if (label != null && label.GetType() == typeof(string))
                            {
                                if (outputType.IsAssignableFrom(type))
                                {
                                    returnableTypes.Add((string)label, type);
                                }
                            }
                        }
                    }
                }
            }
        }

        return returnableTypes;
    }

    public static IDoSomething Create(string id)
    {
        if (ReturnableTypes.ContainsKey(id))
        {
            return (IDoSomething)Activator.CreateInstance(ReturnableTypes[id]);
        }
        else
        {
            throw new Exception("Invalid factory identifier");
        }
    }
}

[Factory("A_ID")]
class A : IDoSomething
{
    public void DoIt()
    {
        Console.WriteLine("Letter A");
    }
}

[Factory("B_ID")]
class B : IDoSomething
{
    public void DoIt()
    {
        Console.WriteLine("Letter B");
    }
}

public interface IDoSomething
{
    void DoIt();
}

/// <summary>
/// Attribute class for decorating classes to use with the generic Factory
/// </summary>
public sealed class FactoryAttribute : Attribute
{
    public IEnumerable<object> Labels { get; private set; }
    public FactoryAttribute(params object[] labels)
    {
        if (labels == null)
        {
            throw new ArgumentNullException("labels cannot be null");
        }
        Labels = labels;
    }
}

/// <summary>
/// Custom exception class for factory creation errors
/// </summary>
public class FactoryCreationException : Exception
{
    public FactoryCreationException()
        : base("Factory failed to create object")
    {
    }
}

/// <summary>
/// Generic Factory class.  Classes must have a parameterless constructor for use with this class.  Decorate classes with 
/// <c>FactoryAttribute</c> labels to match identifiers
/// </summary>
/// <typeparam name="TInput">Input identifier, matches FactoryAttribute labels</typeparam>
/// <typeparam name="TOutput">Output base class / interface</typeparam>
public class Factory<TInput, TOutput>
    where TOutput : class
{
    private static readonly Dictionary<TInput, int> JumpTable;
    private static readonly Func<TInput, TOutput> Creator;

    static Factory()
    {
        JumpTable = new Dictionary<TInput, int>();
        Dictionary<TInput, Type> returnableTypes = GetReturnableTypes();
        int index = 0;
        foreach (KeyValuePair<TInput, Type> kvp in returnableTypes)
        {
            JumpTable.Add(kvp.Key, index++);
        }
        Creator = CreateDelegate(returnableTypes);
    }

    /// <summary>
    /// Creates a TOutput instance based on the label
    /// </summary>
    /// <param name="label">Identifier label to create</param>
    /// <returns></returns>
    public static TOutput Create(TInput label)
    {
        return Creator(label);
    }

    /// <summary>
    /// Creates a TOutput instance based on the label
    /// </summary>
    /// <param name="label">Identifier label to create</param>
    /// <param name="defaultOutput">default object to return if creation fails</param>
    /// <returns></returns>
    public static TOutput Create(TInput label, TOutput defaultOutput)
    {
        try
        {
            return Create(label);
        }
        catch (FactoryCreationException)
        {
            return defaultOutput;
        }
    }

    private static Dictionary<TInput, Type> GetReturnableTypes()
    {
        // get a list of the types that the factory can return
        // criteria for matching types:
        // - must have a parameterless constructor
        // - must have correct factory attribute, with non-null, non-empty value
        // - must have correct BaseType (if OutputType is not generic)
        // - must have matching generic BaseType (if OutputType is generic)

        Dictionary<TInput, Type> returnableTypes = new Dictionary<TInput, Type>();

        Type outputType = typeof(TOutput);
        Type factoryLabelType = typeof(FactoryAttribute);
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            string assemblyName = assembly.GetName().Name;
            if (!assemblyName.StartsWith("System") &&
                assemblyName != "mscorlib" &&
                !assemblyName.StartsWith("Microsoft"))
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(factoryLabelType, false).Length > 0)
                    {
                        foreach (object label in ((FactoryAttribute)type.GetCustomAttributes(factoryLabelType, true)[0]).Labels)
                        {
                            if (label != null && label.GetType() == typeof(TInput))
                            {
                                if (outputType.IsAssignableFrom(type))
                                {
                                    returnableTypes.Add((TInput)label, type);
                                }
                            }
                        }
                    }
                }
            }
        }

        return returnableTypes;
    }

    private static Func<TInput, TOutput> CreateDelegate(Dictionary<TInput, Type> returnableTypes)
    {
        // get FieldInfo reference to the jump table dictionary
        FieldInfo jumpTableFieldInfo = typeof(Factory<TInput, TOutput>).GetField("JumpTable", BindingFlags.Static | BindingFlags.NonPublic);
        if (jumpTableFieldInfo == null)
        {
            throw new InvalidOperationException("Unable to get jump table field");
        }

        // set up the IL Generator
        DynamicMethod dynamicMethod = new DynamicMethod(
            "Magic",                                        // name of dynamic method
            typeof(TOutput),                                // return type
            new[] { typeof(TInput) },                        // arguments
            typeof(Factory<TInput, TOutput>),                // owner class
            true);
        ILGenerator gen = dynamicMethod.GetILGenerator();

        // define labels (marked later as IL is emitted)
        Label creationFailedLabel = gen.DefineLabel();
        Label[] jumpTableLabels = new Label[JumpTable.Count];
        for (int i = 0; i < JumpTable.Count; i++)
        {
            jumpTableLabels[i] = gen.DefineLabel();
        }

        // declare local variables
        gen.DeclareLocal(typeof(TOutput));
        gen.DeclareLocal(typeof(TInput));
        LocalBuilder intLocalBuilder = gen.DeclareLocal(typeof(int));

        // emit MSIL instructions to the dynamic method
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Stloc_1);
        gen.Emit(OpCodes.Volatile);
        gen.Emit(OpCodes.Ldsfld, jumpTableFieldInfo);
        gen.Emit(OpCodes.Ldloc_1);
        gen.Emit(OpCodes.Ldloca_S, intLocalBuilder);
        gen.Emit(OpCodes.Call, typeof(Dictionary<TInput, int>).GetMethod("TryGetValue"));
        gen.Emit(OpCodes.Brfalse, creationFailedLabel);
        gen.Emit(OpCodes.Ldloc_2);
        // execute the MSIL switch statement
        gen.Emit(OpCodes.Switch, jumpTableLabels);

        // set up the jump table
        foreach (KeyValuePair<TInput, int> kvp in JumpTable)
        {
            gen.MarkLabel(jumpTableLabels[kvp.Value]);
            // create the type to return
            gen.Emit(OpCodes.Newobj, returnableTypes[kvp.Key].GetConstructor(Type.EmptyTypes));
            gen.Emit(OpCodes.Ret);
        }

        // CREATION FAILED label
        gen.MarkLabel(creationFailedLabel);
        gen.Emit(OpCodes.Newobj, typeof(FactoryCreationException).GetConstructor(Type.EmptyTypes));
        gen.Emit(OpCodes.Throw);

        // create a delegate so we can later invoke the dynamically created method
        return (Func<TInput, TOutput>)dynamicMethod.CreateDelegate(typeof(Func<TInput, TOutput>));
    }
}

In case anyone needs a version that uses System.Reflection.Emit...

// just paste this into a Console App

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

class Program
{
    static void Main(string[] args)
    {
        // Here's the usage of a "traditional" factory, which returns objects that implement a common interface.
        // This is a great pattern for a lot of different scenarios.
        // The only downside is that you have to update your factory class whenever you add a new class.
        TraditionalFactory.Create("A_ID").DoIt();
        TraditionalFactory.Create("B_ID").DoIt();        
        Console.ReadKey();

        // But what if we make a class that uses reflection to find attributes of classes it can create? Reflection!
        // This works great and now all we have to do is add an attribute to new classes and this thing will just work.
        // (It could also be more generic in its input / output, but I simplified it for this example)
        ReflectionFactory.Create("A_ID").DoIt();
        ReflectionFactory.Create("B_ID").DoIt();
        // Wait, that's great and all, but everyone always says reflection is so slow, and this thing's going to reflect 
        // on every object creation...that's not good right?
        Console.ReadKey();

        // So I created this new factory class which gives the speed of the traditional factory combined with the flexibility 
        // of the reflection-based factory.
        // The reflection done here is only performed once. After that, it is as if the Create() method is using a switch statement        
        Factory<string, IDoSomething>.Create("A_ID").DoIt();
        Factory<string, IDoSomething>.Create("B_ID").DoIt();

        Console.ReadKey();
    }
}

class TraditionalFactory
{
    public static IDoSomething Create(string id)
    {
        switch (id)
        {
            case "A_ID":
                return new A();
            case "B_ID":
                return new B();
            default:
                throw new InvalidOperationException("Invalid factory identifier");
        }
    }
}

class ReflectionFactory
{
    private readonly static Dictionary<string, Type> ReturnableTypes;

    static ReflectionFactory()
    {
        ReturnableTypes = GetReturnableTypes();
    }

    private static Dictionary<string, Type> GetReturnableTypes()
    {
        // get a list of the types that the factory can return
        // criteria for matching types:
        // - must have a parameterless constructor
        // - must have correct factory attribute, with non-null, non-empty value
        // - must have correct BaseType (if OutputType is not generic)
        // - must have matching generic BaseType (if OutputType is generic)

        Dictionary<string, Type> returnableTypes = new Dictionary<string, Type>();

        Type outputType = typeof(IDoSomething);
        Type factoryLabelType = typeof(FactoryAttribute);
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            string assemblyName = assembly.GetName().Name;
            if (!assemblyName.StartsWith("System") &&
                assemblyName != "mscorlib" &&
                !assemblyName.StartsWith("Microsoft"))
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(factoryLabelType, false).Length > 0)
                    {
                        foreach (object label in ((FactoryAttribute)type.GetCustomAttributes(factoryLabelType, true)[0]).Labels)
                        {
                            if (label != null && label.GetType() == typeof(string))
                            {
                                if (outputType.IsAssignableFrom(type))
                                {
                                    returnableTypes.Add((string)label, type);
                                }
                            }
                        }
                    }
                }
            }
        }

        return returnableTypes;
    }

    public static IDoSomething Create(string id)
    {
        if (ReturnableTypes.ContainsKey(id))
        {
            return (IDoSomething)Activator.CreateInstance(ReturnableTypes[id]);
        }
        else
        {
            throw new Exception("Invalid factory identifier");
        }
    }
}

[Factory("A_ID")]
class A : IDoSomething
{
    public void DoIt()
    {
        Console.WriteLine("Letter A");
    }
}

[Factory("B_ID")]
class B : IDoSomething
{
    public void DoIt()
    {
        Console.WriteLine("Letter B");
    }
}

public interface IDoSomething
{
    void DoIt();
}

/// <summary>
/// Attribute class for decorating classes to use with the generic Factory
/// </summary>
public sealed class FactoryAttribute : Attribute
{
    public IEnumerable<object> Labels { get; private set; }
    public FactoryAttribute(params object[] labels)
    {
        if (labels == null)
        {
            throw new ArgumentNullException("labels cannot be null");
        }
        Labels = labels;
    }
}

/// <summary>
/// Custom exception class for factory creation errors
/// </summary>
public class FactoryCreationException : Exception
{
    public FactoryCreationException()
        : base("Factory failed to create object")
    {
    }
}

/// <summary>
/// Generic Factory class.  Classes must have a parameterless constructor for use with this class.  Decorate classes with 
/// <c>FactoryAttribute</c> labels to match identifiers
/// </summary>
/// <typeparam name="TInput">Input identifier, matches FactoryAttribute labels</typeparam>
/// <typeparam name="TOutput">Output base class / interface</typeparam>
public class Factory<TInput, TOutput>
    where TOutput : class
{
    private static readonly Dictionary<TInput, int> JumpTable;
    private static readonly Func<TInput, TOutput> Creator;

    static Factory()
    {
        JumpTable = new Dictionary<TInput, int>();
        Dictionary<TInput, Type> returnableTypes = GetReturnableTypes();
        int index = 0;
        foreach (KeyValuePair<TInput, Type> kvp in returnableTypes)
        {
            JumpTable.Add(kvp.Key, index++);
        }
        Creator = CreateDelegate(returnableTypes);
    }

    /// <summary>
    /// Creates a TOutput instance based on the label
    /// </summary>
    /// <param name="label">Identifier label to create</param>
    /// <returns></returns>
    public static TOutput Create(TInput label)
    {
        return Creator(label);
    }

    /// <summary>
    /// Creates a TOutput instance based on the label
    /// </summary>
    /// <param name="label">Identifier label to create</param>
    /// <param name="defaultOutput">default object to return if creation fails</param>
    /// <returns></returns>
    public static TOutput Create(TInput label, TOutput defaultOutput)
    {
        try
        {
            return Create(label);
        }
        catch (FactoryCreationException)
        {
            return defaultOutput;
        }
    }

    private static Dictionary<TInput, Type> GetReturnableTypes()
    {
        // get a list of the types that the factory can return
        // criteria for matching types:
        // - must have a parameterless constructor
        // - must have correct factory attribute, with non-null, non-empty value
        // - must have correct BaseType (if OutputType is not generic)
        // - must have matching generic BaseType (if OutputType is generic)

        Dictionary<TInput, Type> returnableTypes = new Dictionary<TInput, Type>();

        Type outputType = typeof(TOutput);
        Type factoryLabelType = typeof(FactoryAttribute);
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            string assemblyName = assembly.GetName().Name;
            if (!assemblyName.StartsWith("System") &&
                assemblyName != "mscorlib" &&
                !assemblyName.StartsWith("Microsoft"))
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(factoryLabelType, false).Length > 0)
                    {
                        foreach (object label in ((FactoryAttribute)type.GetCustomAttributes(factoryLabelType, true)[0]).Labels)
                        {
                            if (label != null && label.GetType() == typeof(TInput))
                            {
                                if (outputType.IsAssignableFrom(type))
                                {
                                    returnableTypes.Add((TInput)label, type);
                                }
                            }
                        }
                    }
                }
            }
        }

        return returnableTypes;
    }

    private static Func<TInput, TOutput> CreateDelegate(Dictionary<TInput, Type> returnableTypes)
    {
        // get FieldInfo reference to the jump table dictionary
        FieldInfo jumpTableFieldInfo = typeof(Factory<TInput, TOutput>).GetField("JumpTable", BindingFlags.Static | BindingFlags.NonPublic);
        if (jumpTableFieldInfo == null)
        {
            throw new InvalidOperationException("Unable to get jump table field");
        }

        // set up the IL Generator
        DynamicMethod dynamicMethod = new DynamicMethod(
            "Magic",                                        // name of dynamic method
            typeof(TOutput),                                // return type
            new[] { typeof(TInput) },                        // arguments
            typeof(Factory<TInput, TOutput>),                // owner class
            true);
        ILGenerator gen = dynamicMethod.GetILGenerator();

        // define labels (marked later as IL is emitted)
        Label creationFailedLabel = gen.DefineLabel();
        Label[] jumpTableLabels = new Label[JumpTable.Count];
        for (int i = 0; i < JumpTable.Count; i++)
        {
            jumpTableLabels[i] = gen.DefineLabel();
        }

        // declare local variables
        gen.DeclareLocal(typeof(TOutput));
        gen.DeclareLocal(typeof(TInput));
        LocalBuilder intLocalBuilder = gen.DeclareLocal(typeof(int));

        // emit MSIL instructions to the dynamic method
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Stloc_1);
        gen.Emit(OpCodes.Volatile);
        gen.Emit(OpCodes.Ldsfld, jumpTableFieldInfo);
        gen.Emit(OpCodes.Ldloc_1);
        gen.Emit(OpCodes.Ldloca_S, intLocalBuilder);
        gen.Emit(OpCodes.Call, typeof(Dictionary<TInput, int>).GetMethod("TryGetValue"));
        gen.Emit(OpCodes.Brfalse, creationFailedLabel);
        gen.Emit(OpCodes.Ldloc_2);
        // execute the MSIL switch statement
        gen.Emit(OpCodes.Switch, jumpTableLabels);

        // set up the jump table
        foreach (KeyValuePair<TInput, int> kvp in JumpTable)
        {
            gen.MarkLabel(jumpTableLabels[kvp.Value]);
            // create the type to return
            gen.Emit(OpCodes.Newobj, returnableTypes[kvp.Key].GetConstructor(Type.EmptyTypes));
            gen.Emit(OpCodes.Ret);
        }

        // CREATION FAILED label
        gen.MarkLabel(creationFailedLabel);
        gen.Emit(OpCodes.Newobj, typeof(FactoryCreationException).GetConstructor(Type.EmptyTypes));
        gen.Emit(OpCodes.Throw);

        // create a delegate so we can later invoke the dynamically created method
        return (Func<TInput, TOutput>)dynamicMethod.CreateDelegate(typeof(Func<TInput, TOutput>));
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文