У меня в настоящее время есть 2 конкретных метода в 2 абстрактных классах. Один класс содержит существующий метод, в то время как другой содержит метод прежней версии. Например.
// Class #1
public abstract class ClassCurrent<T> : BaseClass<T> where T : BaseNode, new()
{
public List<T> GetAllRootNodes(int i)
{
//some code
}
}
// Class #2
public abstract class MyClassLegacy<T> : BaseClass<T> where T : BaseNode, new()
{
public List<T> GetAllLeafNodes(int j)
{
//some code
}
}
Я хочу, чтобы соответствующий метод работал в их относительных сценариях в приложении. Я планирую записать делегату для обработки этого. Идея состоит в том, что я могу просто позвонить делегату и записать логику в ней для обработки, для какого метода звонить, в зависимости от которого класса/проекта это называют от (по крайней мере это - то, что я думаю, делегаты и как они используются).
Однако у меня есть некоторые вопросы по той теме (после некоторого поиска с помощью Google):
1) Действительно ли возможно иметь делегата, который знает 2 (или больше) методы, которые находятся в различных классах? 2) Действительно ли возможно сделать делегата, который мечет икру от абстрактных классов (как из вышеупомянутого кода)? (Мое предположение не, так как делегаты создают конкретную реализацию переданного - в классах), 3), я пытался записать делегату к вышеупомянутому коду. Но мне технически бросают вызов:
public delegate List<BaseNode> GetAllNodesDelegate(int k);
GetAllNodesDelegate del = new GetAllNodesDelegate(ClassCurrent<BaseNode>.GetAllRootNodes);
Я получил следующую ошибку:
An object reference is required for the non-static field, method, property ClassCurrent<BaseNode>.GetAllRootNodes(int)
Я, возможно, неправильно понял что-то..., но если я должен вручную объявить делегата в классе вызова, И передать в функции вручную как выше, затем я начинаю подвергать сомнению, является ли делегат хорошим способом решить мою проблему.
Спасибо.
То, как вы пытаетесь использовать делегаты (конструирование их с помощью new
, объявление именованного типа делегата), предполагает, что вы используя C # 1. Если вы действительно используете C # 3, это намного проще.
Во-первых, тип вашего делегата:
public delegate List<BaseNode> GetAllNodesDelegate(int k);
Уже существует. Это просто:
Func<int, List<BaseNode>>
Так что вам не нужно объявлять свою собственную версию.
Во-вторых, вы должны думать о делегате как о интерфейсе, в котором есть только один метод, и вы можете «реализовать» его на лету, без необходимости писать именованный класс. Просто напишите лямбда или назначьте имя метода напрямую.
Func<int, List<BaseNode>> getNodesFromInt;
// just assign a compatible method directly
getNodesFromInt = DoSomethingWithArgAndReturnList;
// or bind extra arguments to an incompatible method:
getNodesFromInt = arg => MakeList(arg, "anotherArgument");
// or write the whole thing specially:
getNodesFromInt = arg =>
{
var result = new List<BaseNode>();
result.Add(new BaseNode());
return result;
};
Лямбда имеет вид (аргументы) => {тело; }
. Аргументы разделяются запятыми. Если есть только один, скобки можно опустить. Если он не принимает никаких параметров, заключите пару пустых скобок: ()
. Если тело состоит только из одного оператора, фигурные скобки можно опустить. Если это всего лишь одно выражение, вы можете опустить фигурные скобки и ключевое слово return
. В теле вы можете ссылаться практически на любые переменные и методы из охватывающей области (кроме параметров ref
/ out
на включающий метод).
Практически никогда не требуется использовать new
для создания экземпляра делегата. И редко возникает необходимость объявлять настраиваемые типы делегатов.Используйте Func
для делегатов, возвращающих значение, и Action
для делегатов, возвращающих void
.
Всякий раз, когда вещь, которую вам нужно передать, похожа на объект с одним методом (будь то интерфейс или класс), используйте вместо этого делегат, и вы сможете избежать большого беспорядка.
В частности, избегайте определения интерфейсов одним методом. Это будет просто означать, что вместо возможности написать лямбда для реализации этого метода вам придется объявить отдельный именованный класс для каждой другой реализации с шаблоном:
class Impl : IOneMethod
{
// a bunch of fields
public Impl(a bunch of parameters)
{
// assign all the parameters to their fields
}
public void TheOneMethod()
{
// make use of the fields
}
}
Лямбда эффективно делает все это за вас, исключая такие механические шаблоны из вашего кода. Вы просто говорите:
() => /* same code as in TheOneMethod */
Это также имеет то преимущество, что вы можете обновлять переменные во включающей области видимости, потому что вы можете ссылаться на них напрямую (вместо работы со значениями, скопированными в поля класса). Иногда это может быть недостатком, если вы не хотите изменять значения.
В качестве варианта темы, предложенной Руне Гримстад, я думаю, вы могли бы использовать шаблон стратегии (например,
Введение в шаблон стратегии GOF в C # ).
Это было бы особенно интересно в случае, когда вы не можете изменить LegacyClass (и, следовательно, возможно, не можете легко использовать «интерфейсный подход», предложенный Корнелиусом), и если вы используете внедрение зависимостей (DI; Injection Dependency ]). Я бы (возможно) позволил вам внедрить правильную реализацию (конкретную стратегию) в нужное место.
Стратегия:
public interface INodeFetcher<T> {
List<T> GetNodes(int k);
}
Конкретные стратегии:
public class CurrentSelector<T> : INodeFetcher<T>
{
public List<T> GetNodes(int argument)
{
// Return the result "current" method
}
}
public class LegacySelector<T> : INodeFetcher<T>
{
public List<T> GetNodes(int argument)
{
// Return the result "legacy" method
}
}
-> Внедрить / реализовать правильную конкретную стратегию.
С уважением
У вас может быть делегат, который инициализируется ссылками на различные методы в зависимости от некоторых условий.
Что касается ваших вопросов:
1) Я не уверен, что вы имеете в виду под "знает". Вы можете передать делегату любой метод, поэтому если вы можете написать метод, который "знает" о некоторых других методах, то вы можете сделать аналогичный делегат.
2) Опять же, делегаты могут быть созданы из любого метода, который может быть выполнен. Например, если у вас есть инициализированная локальная переменная типа ClassCurrent
, вы можете создать делегат для любого метода экземпляра типа ClassCurrent
.
3) Делегат может вызывать только тот метод, который действительно может быть вызван. То есть, вы не можете вызвать ClassCurrent.GetAllRootNodes
, потому что GetAllRootNodes
не является статическим методом, поэтому для его вызова нужен экземпляр ClassCurrent
.
Делегат может находиться в любом классе, который имеет доступ к ClassCurrent
и MyClassLegacy
.
Например, вы можете создать что-то вроде:
class SomeActionAccessor<T>
{
// Declare delegate and fied of delegate type.
public delegate T GetAllNodesDelegate(int i);
private GetAllNodesDelegate getAllNodesDlg;
// Initilaize delegate field somehow, e.g. in constructor.
public SomeActionAccessor(GetAllNodesDelegate getAllNodesDlg)
{
this.getAllNodesDlg = getAllNodesDlg;
}
// Implement the method that calls the delegate.
public T GetAllNodes(int i)
{
return this.getAllNodesDlg(i);
}
}
Делегаты могут обертывать как статический метод, так и метод экземпляра. Единственная разница в том, что для создания делегата с методом экземпляра вам нужен экземпляр класса, которому принадлежит метод.
Зачем вам для этого нужен делегат? Звучит слишком сложно. Я бы просто создал метод в новом классе, который вы могли бы создать, когда вам нужно было вызвать свой метод. Этому классу может быть предоставлена некоторая контекстная информация, которая поможет ему принять решение. Затем я бы реализовал логику в новом методе, которая решала бы, вызывать ли текущий метод или унаследованный метод.
Примерно так:
public class CurrentOrLegacySelector<T>
{
public CurrentOrLegacySelector(some type that describe context)
{
// .. do something with the context.
// The context could be a boolean or something more fancy.
}
public List<T> GetNodes(int argument)
{
// Return the result of either current or
// legacy method based on context information
}
}
Это даст вам чистую оболочку для методов, которую легко читать и понимать.
Пусть и ClassCurrent
, и MyClassLegacy
реализуют интерфейс INodeFetcher
:
public interface INodeFetcher<T> {
List<T> GetNodes(int k);
}
Для ClassCurrent
вызвать метод GetAllRootNodes
из реализации интерфейса, а для MyLegacyClass
метод GetAllLeaveNodes
.