делегат c# и абстрактный класс

У меня в настоящее время есть 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)

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

Спасибо.

6
задан Jon Skeet 17 March 2010 в 07:23
поделиться

5 ответов

То, как вы пытаетесь использовать делегаты (конструирование их с помощью 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 */

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

9
ответ дан 9 December 2019 в 22:32
поделиться

В качестве варианта темы, предложенной Руне Гримстад, я думаю, вы могли бы использовать шаблон стратегии (например,
Введение в шаблон стратегии 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
    }
}

-> Внедрить / реализовать правильную конкретную стратегию.

С уважением

0
ответ дан 9 December 2019 в 22:32
поделиться

У вас может быть делегат, который инициализируется ссылками на различные методы в зависимости от некоторых условий.

Что касается ваших вопросов:
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);
    }
}

Делегаты могут обертывать как статический метод, так и метод экземпляра. Единственная разница в том, что для создания делегата с методом экземпляра вам нужен экземпляр класса, которому принадлежит метод.

1
ответ дан 9 December 2019 в 22:32
поделиться

Зачем вам для этого нужен делегат? Звучит слишком сложно. Я бы просто создал метод в новом классе, который вы могли бы создать, когда вам нужно было вызвать свой метод. Этому классу может быть предоставлена ​​некоторая контекстная информация, которая поможет ему принять решение. Затем я бы реализовал логику в новом методе, которая решала бы, вызывать ли текущий метод или унаследованный метод.

Примерно так:

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

Это даст вам чистую оболочку для методов, которую легко читать и понимать.

0
ответ дан 9 December 2019 в 22:32
поделиться

Пусть и ClassCurrent, и MyClassLegacy реализуют интерфейс INodeFetcher:

public interface INodeFetcher<T> { 
    List<T> GetNodes(int k);
} 

Для ClassCurrent вызвать метод GetAllRootNodes из реализации интерфейса, а для MyLegacyClass метод GetAllLeaveNodes.

1
ответ дан 9 December 2019 в 22:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: