Передача метода в качестве аргумента

#c# #delegates

#c# #делегирует

Вопрос:

У меня есть метод SaveChanges<T>(T object) , который часто вызывается во всем моем коде, за исключением того, что в зависимости от действия, вызывающего метод, будет другой метод, вызываемый из SaveChanges. Что-то вроде этого…

 protected void SaveChanges<T>(T mlaObject, SomeFunction(arg))
    where T : WebObject
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        SomeFunction(arg);
    }
}
  

Примеры использования:

 SaveChanges<MlaArticle>(article, article.Authors.Remove(person)) //person is an object of type MlaPerson
//OR
SaveChanges<MlaArticle>(article, article.RelatedTags.Remove(tag)) //tag is an object of type Tag
//OR
SaveChanges<MlaArticle>(article, article.RelatedWebObjects.Remove(location)) //location is an object of type MlaLocation
  

Я прочитал о методах делегирования, но я немного смущен тем, как реализовать это в соответствии с моими требованиями или если мои требования вообще требуют использования для делегатов.

РЕДАКТИРОВАТЬ: Кроме того, можно ли передать несколько действий?

Ответ №1:

Как насчет:

 protected void SaveChanges<T>(T mlaObject, Action<T> rollback)
    where T : WebObject
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        rollback(mlaObject);
    }
}
  

Вызывается как:

 this.SaveChanges(myObj, x => article.Authors.Remove(x));
  

Теперь, после второго прочтения вашего вопроса, я не вижу смысла передавать mlaObject , поскольку он никогда не используется.

 // this.SaveChanges(
//     () => article.Authors.Remove(author),
//     () => article.RelatedTags.Remove(tag));
protected void SaveChanges(params Action[] rollbacks)
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        foreach (var rollback in rollbacks) rollback();
    }
}

// Overload to support rollback with an argument
// this.SaveChanges(
//     author,
//     article.Authors.Remove,
//     authorCache.Remove);
protected void SaveChanges<T>(T arg, params Action<T>[] rollbacks)
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        foreach (var rollback in rollbacks) rollback(arg);
    }
}
  

Комментарии:

1. Вам нужно добавить параметр arg .

2. Мне это нравится, очень прямолинейно. Можно ли отправить несколько действий?

3. Конечно, вы могли бы использовать IEnumerable<Действие<T>> или само ваше действие<T> может указывать любое количество вызываемых объектов, например x => { doA(x); doB(x); doC(x); }

4. @GeorgeDuckett: спасибо, я интуитивно понял, что это так mlaObject . Но это может быть не так при втором чтении.

5. @bflemi3: добавлена поддержка нескольких действий, удалено неиспользуемое использование mlaObject .

Ответ №2:

ОБНОВЛЕНИЕ Мне было немного непонятно из вашего вопроса, если переданный аргумент используется где-либо еще в методе, похоже, что это не так, поэтому вы можете просто взять Action и использовать лямбда-выражение, чтобы указать делегата для вызова с захваченным аргументом:

 protected void SaveChanges<T, TArg>(T mlaObject, TArg arg, Action undoFunction)
    where T : WebObject
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        undoFunction();
    }
}
  

Которому вы можете передать:

 SaveChanges(article, () => article.Authors.Remove(person));
  

Или, если это сам myObj, в этом случае (как уже ответили sixlettervariables) вы можете просто передать его обратно в делегате в соответствии с его кодом.

Или, отличается ли аргумент от mlaObject, и вы хотите также выполнять другие действия с ним в коде, и в этом случае вы могли бы сделать:

 protected void SaveChanges<T, TArg>(T mlaObject, TArg arg, Action undoFunction)
    where T : WebObject
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        undoFunction(arg);
    }
}
  

И затем иметь:

 SaveChanges(article, person, article.Authors.Remove);
  

Комментарии:

1. Я надеюсь, что мне больше не придется работать с таким кодом без исходного кода или документации ;-).

2. Ну, в идеале у вас должно быть хотя бы несколько хороших /// комментариев к нему. Делегирование действий / функций быстро становится второй натурой, особенно когда вы начинаете играть с LINQ.

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

4. Мой единственный вопрос с этим кодом заключается в том, какова полезность T mlaObject , если он не используется. Возможно, это не вопрос для @JamesMichaelHare, но, безусловно, для OP 🙂

5. Очень хороший вопрос, если он не используется, то, конечно, его можно удалить, в противном случае я предполагал, что в нем будет какое-то использование, ведущее к this._db.SaveChanges() .

Ответ №3:

 protected void SaveChanges<T,U>(T mlaObject, Action<U> action, U arg)
    where T : WebObject
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        action(arg);
    }
}
  

Надеюсь, я правильно понял вопрос…

Комментарии:

1. да, вы все правильно поняли. можно ли отправить несколько действий?

Ответ №4:

Ваш SaveChanges метод будет выглядеть примерно так:

 protected void SaveChanges<T,TArg>(T mlaObject, TArg arg, Action<T,TArg> someFunction)
    where T : WebObject
{
   ...
}
  

Вызывается как:

 SaveChanges<MlaArticle,Person>(article,person, (article,person) =>  article.Authors.Remove(person))
  

Комментарии:

1. Вам нужно будет указать тип обоих общих аргументов, если вы укажете один из них.

Ответ №5:

если мои требования вообще требуют использования для делегатов.

Если вы хотите SaveChanges , чтобы метод выполнял какую-либо функцию, у вас есть два варианта

  • пусть он выполняет функцию напрямую (кодирует внутри метода или вызывает какой-либо второй метод изнутри метода); или
  • предоставьте функцию SaveChanges методу в качестве делегата.

Когда использовать каждый из них — это выбор дизайна, который зависит от сценария, общего решения и ваших предпочтений.

Преимущества первого

  • Возможность видеть все возможные результаты SaveChanges метода в одном месте
  • Менее запутанный для тех, кто не знает, как работают делегаты

Преимущества второго

  • Возможность исключить все возможные функции из SaveChanges метода (это не требует огромного case или if else if else if )
  • Функции, которые передаются SaveChanges методу, могут находиться над ним в стеке вызовов, ему не нужно знать, что это такое или как они работают, они могут делать вещи, которые он не понимает, и их можно использовать повторно — вызывать в другом месте или использовать в качестве делегатов в других функциях.

Я думаю, что первый пункт здесь является основным. Если вы обрабатываете только пару сценариев, тогда можно использовать an if else if else if , но если вы получаете больше, чем несколько вариантов, и предпочитаете более общий SaveChanges метод, тогда psdd этот делегат.

Ответ №6:

 protected void SaveChanges<T>(T mlaObject, Action<T> functionToCall)
{
    try { this._db.SaveChanges(); }
    catch (Exception e)
    {
        Console.WriteLine("Error: "   e);
        functionToCall(mlaObject);
    } 
}
  

Вызовите так:

 SaveChanges(actualArticle, article => article.Authors.Remove(person));
  

Я пропустил бит WebObject, поскольку он вообще не использовался в функции.