2013-04-24 4 views
4

Я пишу программу на C# (3.5 на данный момент, но, возможно, ее можно адаптировать к другим версиям по мере необходимости), которая использует простую архитектуру плагина для управления вводом и выводом. Каждый плагин представляет собой DLL, которая загружается, когда пользователь выбирает плагины для использования.C# Generic Method vs Casting

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

До сих пор я использую следующие для вызова методов плагина:

public object Method(string methodName, params object[] arguments) { 
    // Assumed variables/methods/exceptions: 
    // Dictionary<string, MethodInfo> Methods: a cache of MethodInfo's 
    //  of previously called methods. 
    // NoSuchMethodException: thrown if an unknown/unreachable method is 
    //  requested. The message member contains the invalid method name 
    // void LoadMethod(string methodName, params object[] arguments): responsible 
    //  for retrieving the MethodInfo's, or throw a NoSuchMethodException 
    // object Plugin: an instance of the dynamically loaded class. 
    if (!Methods.ContainsKey(methodName)) { 
    LoadMethod(methodName, arguments); 
    } 
    if (arguments != null && arguments.Length == 0) { 
    arguments = null; 
    } 
    return Methods[methodName].Invoke(Plugin, arguments); 
} 

Который используется как:

string[] headers = (string[]) Plugin.Method("GetHeaders", dbName, tableName); 

Это прекрасно работает, до тех пор, вызывающему правильно возвращает возвращаемое значение в ожидаемый тип . Плагины должны реализовывать определенные интерфейсы, поэтому вызывающий должен знать этот тип.

После выполнения некоторых дальнейшей работы с отражением, однако, следующий альтернативная форма пришла мне в голову:

public T Method<T>(string methodName, params object[] arguments) { 
    if (!Methods.ContainsKey(methodName)) { 
    LoadMethod(methodName, arguments); 
    } 
    if (Methods[methodName].ReturnType != typeof(T)) { 
    // Could also move this into LoadMethod to keep all the throwing in one place 
    throw new NoSuchMethodException(methodName); 
    } 
    if (arguments != null && arguments.Length == 0) { 
    arguments = null; 
    } 
    return (T) Methods[methodName].Invoke(Plugin, arguments); 
} 

Это один используется как:

string[] headers = Plugin.Method<string[]>("GetHeaders", dbName, tableName); 

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

public void Method(string methodName, params object[] arguments) { 
    // Good for void methods, or when you're going to throw away the return 
    // value anyway. 
    if (!Methods.ContainsKey(methodName)) { 
    LoadMethod(methodName, arguments); 
    } 
    if (arguments != null && arguments.Length == 0) { 
    arguments = null; 
    } 
    Methods[methodName].Invoke(Plugin, arguments); 
} 

Мой вопрос - это один из них внутренне лучше, чем другой (для данного значения «лучше») ? Например, это особенно заметно быстрее? Легче понять? Больше поддерживается?

Мне лично нравится внешний вид последнего, хотя я немного обеспокоен тем, что мое тестирование типа возвращаемого типа (Methods[methodName].ReturnType != typeof(T)) может быть слишком упрощенным. Интересно, что это было изначально !(Methods[methodName].ReturnType is T), но это всегда казалось неудачным.

Ближайший существующий вопрос к этому, который я мог найти, был Generic method to type casting, и некоторые из ответов предполагали, что последний метод дороже первого, но там не так много деталей (вопрос о том, реализация метода, а не лучше).

Уточнение: Это ручная, очень ограниченная система плагинов, не использующая IPlugin. Меня больше интересует вопрос о том, являются ли общие методы по своей природе лучше/хуже, чем ожидание вызова в этой ситуации.

+1

Ум в обоих направлениях, похоже, делает то же самое? Один из них использует метод, другой - снаружи? – lahsrah

+0

Почему вы используете детали реализации из плагинов? – ChaosPandion

+0

@sylon Я знаю, что они оба делают одно и то же - мой вопрос в том, какой (если есть) «лучше». – MDB

ответ

3

Что касается вашего вопроса, я думаю, вы должны предоставить оба. Просто введите общую версию с нестандартной версией. Вы получаете лучшее из обоих миров. Что касается производительности, подумайте о том, насколько малы время, затрачиваемое на создание объекта, по сравнению с динамическим вызовом метода и создания массива объектов и объектов. Это действительно не стоит принимать во внимание здесь. Я предпочитаю общий подход со стилистической точки зрения, но также считаю, что при необходимости можно применять ограничения типа.

public T Method<T>(string methodName, params object[] arguments) 
{ 
    return (T)Method(methodName, arguments); 
} 

Side Bar

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

var plugin = (IPlugin)Activator.CreateInstance(pluginType); 
var headers = plugin.GetHeaders(dbName, tableName); 

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

public interface IPlugin 
{ 
    void Load(IAppContext context); 
    void Unload(); 
} 

Ваш способ загрузки может выглядеть следующим образом.

void LoadPlugins(Assembly a) 
{ 
    var plugins = 
     a.GetTypes() 
     .Where(t => typeof(IPlugin).IsAssignableFrom(t)) 
     .Select(t => (IPlugin)Activator.CreateInstance(t)) 
     .ToList(); 
    Plugins.AddRange(plugins); 
    foreach (var p in plugins) 
    { 
     p.Load(Context); 
    } 
} 
+1

Я, возможно, оставлю оба доступными только ради удобства в любом случае (хотя он угадывает версию метода void, так как он сталкивается со «старой» версией, но это только я, что я не знаю). И спасибо за информацию, основанную на IPlugin, но я на самом деле не использую IPlugin - это ручная система с ограниченным подключением. Добавлено уточнение вопроса. – MDB

+0

@MDB - для примера я создал интерфейс IPlugin. Вы могли бы назвать это чем угодно. – ChaosPandion