Фабрика создания объектов в соответствии с универсальным типом C#

Что было бы наиболее эффективным способом создания экземпляра объекта в соответствии с общим типом, переданным классу Factory, например:

public class LoggerFactory
{
    public static ILogger<T> Create<T>()
    {
        // Switch Statement?
        // Generic Dictionary?
        // EX.: if "T" is of type "string": return (ILogger<T>)new StringLogger();
    }
}

Как бы вы это сделали? Какой оператор ветвления? так далее...


person maxbeaudoin    schedule 17.07.2009    source источник


Ответы (8)


Я думаю, что лучше сделать это просто, возможно, что-то вроде этого:

public static class LoggerFactory
{
    static readonly Dictionary<Type, Type> loggers = new Dictionary<Type, Type>();

    public static void AddLoggerProvider<T, TLogger>() where TLogger : ILogger<T>, new()
    {
        loggers.Add(typeof(T), typeof(TLogger));
    }

    public static ILogger<T> CreateLogger<T>()
    {
        //implement some error checking here
        Type tLogger = loggers[typeof(T)];

        ILogger<T> logger = (ILogger<T>) Activator.CreateInstance(tLogger);

        return logger;
    }
}

Вы просто вызываете AddLoggerProvider для каждого типа, который хотите поддерживать, может быть расширен во время выполнения, это гарантирует, что вы определенно добавите реализацию интерфейса в библиотеку, а не какой-либо объект, не очень быстро из-за Activator, но создание регистратор вряд ли будет узким местом в любом случае. Надеюсь, это выглядит нормально.

Использование:

// initialize somewhere
LoggerFactory.AddLoggerProvider<String, StringLogger>();
LoggerFactory.AddLoggerProvider<Exception, ExceptionLogger>();
// etc..

ILogger<string> stringLogger = LoggerFactory.CreateLogger<string>();

Примечание: для каждого ILogger<T> требуется конструктор без параметров для Activator, но это также обеспечивается общим ограничением new() в методе добавления.

person Kenan E. K.    schedule 17.07.2009
comment
На самом деле вы обнаружите, что активатор работает очень медленно: csharp-architect.com/post/2009/06/11/ - person Prisoner ZERO; 05.10.2011
comment
Я написал не очень быстро из-за активатора, так что это не эксклюзив с очень медленным :) - person Kenan E. K.; 03.11.2011

Я думаю, что я бы сделал это так:

public class LoggerFactory<T>
{
    private static Dictionary<Type, Func<ILogger<T>>> LoggerMap = 
        new Dictionary<Type, Func<ILogger<T>>>
    {
        { typeof(string), 
            () => new StringILogger() as ILogger<T> },
        { typeof(StringWriter), 
            () => new StringWriterILogger() as ILogger<T> }
    };

    public static ILogger<T> CreateLogger()
    {
        return LoggerMap[typeof(T)]();
    }
}

Вы платите что-то вроде цены за удобочитаемость (все эти угловые скобки, блин), но, как вы можете видеть, это делает очень мало логики программы.

person Robert Rossney    schedule 17.07.2009

Хотя я обычно рекомендую использовать инфраструктуру внедрения зависимостей, вы можете реализовать что-то с отражением, которое будет искать доступные типы для того, который реализует соответствующий интерфейс ILogger.

Я бы посоветовал вам тщательно продумать, какие сборки будут содержать эти реализации регистратора и насколько расширяемым и пуленепробиваемым должно быть решение. Выполнение поиска во время выполнения по доступным сборкам и типам стоит недёшево. Однако это простой способ обеспечить расширяемость в этом типе дизайна. Это также позволяет избежать проблемы предварительной настройки — однако требуется, чтобы только один конкретный тип реализовывал конкретную версию интерфейса ILogger‹> — в противном случае возникает неоднозначная ситуация, которую необходимо разрешить.

Вы можете захотеть выполнить некоторое внутреннее кэширование, чтобы избежать затрат на выполнение отражения при каждом вызове Create().

Вот пример кода, с которого вы можете начать.

using System;
using System.Linq;
using System.Reflection;

public interface ILogger<T> { /*... */}

public class IntLogger : ILogger<int> { }

public class StringLogger : ILogger<string> { }

public class DateTimeLogger : ILogger<DateTime> { }

public class LoggerFactory
{
    public static ILogger<T> Create<T>()
    {
        // look within the current assembly for matching implementation
        // this could be extended to search across all loaded assemblies
        // relatively easily - at the expense of performance
        // also, you probably want to cache these results...
        var loggerType = Assembly.GetExecutingAssembly()
                     .GetTypes()
                     // find implementations of ILogger<T> that match on T
                     .Where(t => typeof(ILogger<T>).IsAssignableFrom(t))
                     // throw an exception if more than one handler found,
                     // could be revised to be more friendly, or make a choice
                     // amongst multiple available options...
                     .Single(); 

        /* if you don't have LINQ, and need C# 2.0 compatibility, you can use this:
        Type loggerType;
        Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes();
        foreach( var type in allTypes )
        {
            if( typeof(ILogger<T>).IsAssignableFrom(type) && loggerType == null )
                loggerType = type;
            else
                throw new ApplicationException( "Multiple types handle ILogger<" + typeof(T).Name + ">" );                   
        }

        */

        MethodInfo ctor = loggerType.GetConstructor( Type.EmptyTypes );
        if (ctor != null)
            return ctor.Invoke( null ) as ILogger<T>;

        // couldn't find an implementation
        throw new ArgumentException(
          "No mplementation of ILogger<{0}>" + typeof( T ) );
    }
}

// some very basic tests to validate the approach...
public static class TypeDispatch
{
    public static void Main( string[] args )
    {
        var intLogger      = LoggerFactory.Create<int>();
        var stringLogger   = LoggerFactory.Create<string>();
        var dateTimeLogger = LoggerFactory.Create<DateTime>();
        // no logger for this type; throws exception...
        var notFoundLogger = LoggerFactory.Create<double>(); 
    }
}
person LBushkin    schedule 17.07.2009
comment
Это требует LINQ, C # 3.0, и я 2.0, но я мог бы рассмотреть структуру внедрения зависимостей, учитывая вас, ребята. - person maxbeaudoin; 17.07.2009

Зависит от того, сколько типов вы собираетесь обрабатывать. Если он маленький (менее 10), я бы предложил оператор switch, так как он будет быстрым и понятным для чтения. Если вам нужно больше, вам понадобится таблица поиска (хеш-карта, словарь и т. д.) или какая-либо система, основанная на отражении.

person C. Ross    schedule 17.07.2009
comment
Вероятно, в конечном итоге оно превысит 10, но пример таблицы поиска был бы интересен, если бы он у вас был. Я не в восторге от сравнения типов как строк. - person maxbeaudoin; 17.07.2009
comment
Словарь‹Тип,Тип› может удовлетворить ваши потребности. - person C. Ross; 17.07.2009
comment
Да, действительно, но в C# 2.0 я не могу инициализировать его статически. private static Dictionary‹Type, Type› loggerTable = new Dictionnary‹Type, Type›() { {string, StringLogger} } Не работает - person maxbeaudoin; 17.07.2009
comment
Инициализировать в конструкторе, возможно, тогда? - person C. Ross; 18.07.2009

Оператор switch против словаря - не имеет значения для производительности, поскольку переключатель скомпилирован в словарь. Так что на самом деле это вопрос читабельности и гибкости. Переключатель легче читать, с другой стороны, словарь можно расширить во время выполнения.

person Scott Weinstein    schedule 17.07.2009
comment
Будут ли преимущества расширения словаря Factory во время выполнения? - person maxbeaudoin; 17.07.2009
comment
Switch компилируется в словарь? Звучит подозрительно. На основании какой информации вы это делаете? - person Judah Gabriel Himango; 17.07.2009
comment
В зависимости от контекста, switch может быть скомпилирован в dict. Я читал статью об этом, и суть в том, что декомпиляция CLI в C# может быть ужасной, поскольку существует несколько форм, и Dict является одной из них. - person Dykam; 17.07.2009
comment
Иуда, судя по тому, что ИЛ смотрит в рефлектор. - person Scott Weinstein; 17.07.2009
comment
Бод, Только если это твое требование - person Scott Weinstein; 17.07.2009

Здесь вы можете рассмотреть возможность использования инфраструктуры внедрения зависимостей, такой как Unity. Вы можете настроить его с помощью общих типов, которые будет возвращать ваш фактор, и выполнить сопоставление в конфигурации. Вот пример.

person JP Alioto    schedule 17.07.2009
comment
Рассматривал это, но я хотел бы более простое решение в коде. +1 за это решение. - person maxbeaudoin; 17.07.2009
comment
Да, понятно, но для полноты ответа вы можете настроить контейнер Unity в коде. :) - person JP Alioto; 18.07.2009

1) Меня всегда поражала сложность, которую люди вкладывают в ведение журнала. Мне всегда кажется излишеством. Если log4net с открытым исходным кодом, я бы порекомендовал вам взглянуть на это, на самом деле, вы могли бы просто использовать его ...

2) Лично я стараюсь по возможности избегать проверки типов - это побеждает смысл дженериков. Просто используйте метод .ToString() и покончите с этим.

person Community    schedule 17.07.2009

Хм... на самом деле вы могли бы попытаться быть немного умнее, в зависимости от того, что поддерживает данная система времени выполнения. На самом деле я стараюсь избегать любых условных операторов в своем коде, если могу, особенно в полиморфном и динамически связанном коде. У вас есть общий класс, так почему бы не использовать его?

Например, в Java вы можете особенно использовать статический метод, который у вас есть, чтобы сделать что-то вроде этого:

public class LoggerFactory<T>
{
    public static ILogger<T> CreateLogger(Class<? extends SomeUsefulClass> aClass);
    {
        // where getLogger() is a class method SomeUsefulClass and its subclasses
        // and has a return value of Logger<aClass>.
        return aClass.getLogger();

        // Or perhaps you meant something like the below, which is also valid.
        // it passes the generic type to the specific class' getLogger() method
        // for correct instantiation. However, be careful; you don't want to get
        // in the habit of using generics as variables. There's a reason they're
        // two different things.

        // return aClass.getLogger(T);
    }
}

Вы бы назвали это так:

public static void main(String[] args)
{
    Logger = LoggerFactory.createLogger(subclassOfUsefulClass.class);
    // And off you go!
}

Это позволяет избежать необходимости иметь какие-либо условные выражения и, кроме того, является более гибким: любой класс, который является подклассом (или, возможно, реализует интерфейс регистратора) SomeUsefulClass, может возвращать правильно типизированный экземпляр регистратора.

person Omar Zakaria    schedule 17.07.2009
comment
Мне нужно вернуть правильно типизированный регистратор в соответствии с Generic, который может быть любого типа. Например, исключение, переданное в фабрику, вернет ExceptionLogger. Это нужно где-то отобразить. - person maxbeaudoin; 17.07.2009