简体   繁体   中英

How to pass in a method as a parameter?

I've just noticed I'm repeating a lot of C# code in my ASP.NET application so want to create a generic method. I have a series of private methods like this:

private void PopulateMyRepeatedControl()
{
    DBUtil DB = new DBUtil();
    DataTable symbols = GetSelectedSymbols();
    DataTable tradeGrades = GetSelectedTradeGrades();
    DataTable executionGrades = GetSelectedExecutionGrades();        

    chtMyRepeatedChart.DataSource = DB.MyRepeatedCall (
        int.Parse(txtStartBalance.Text),
        int.Parse(ddlTradeTypes.SelectedValue),
        ddlRepeatedTrades.SelectedValue,
        radSide.SelectedValue,
        ddlTradeSetups.SelectedValue,
        symbols,
        ddlChartTimeFrames.SelectedValue,
        int.Parse(ddlHours.SelectedValue),
        int.Parse(ddlYears.SelectedValue),
        int.Parse(ddlMonths.SelectedValue),
        int.Parse(ddlDays.SelectedValue),
        int.Parse(ddlNumSCs.SelectedValue),
        txtDateFrom.Text,
        txtDateTo.Text,
        tradeGrades,
        executionGrades,
        int.Parse(txtMinProfitPips.Text),
        int.Parse(txtMaxProfitPips.Text));

    chtMyRepeatedChart.DataBind();
}

So, I want to replace DB.MyRepeatedCall , chtMyRepeatedChart and pass them in as parameters to a generic function. Is that possible? I have many charts on my form that take the same number of parameters.

Thanks

UPDATE Following Frederik's solution I have done this:

private delegate IEnumerable<DataTable> GetDataSource(
    int TradeType,
    string RepeatedTrades,
    string Side,
    string TradeSetup,
    DataTable symbols,
    string ChartTimeFrame,
    int Hour,
    int Year,
    int Month,
    int Day,
    int NumSCs,
    string DateFrom,
    string DateTo,
    DataTable TradeGrades,
    DataTable ExecutionGrades,
    int MinProfitPips,
    int MaxProfitPips);

private void PopulateControl(Chart chart, GetDataSource getDataSource)
{
    //DBUtil DB = new DBUtil();
    DataTable symbols = GetSelectedItems("symbol", listSymbols);
    DataTable tradeGrades = GetSelectedItems("tradeGrade", listTradeGrades);
    DataTable executionGrades = GetSelectedItems("executionGrade", listExecutionGrades);

    chart.DataSource = getDataSource(
        int.Parse(ddlTradeTypes.SelectedValue),
        ddlRepeatedTrades.SelectedValue,
        radSide.SelectedValue,
        ddlTradeSetups.SelectedValue,
        symbols,
        ddlChartTimeFrames.SelectedValue,
        int.Parse(ddlHours.SelectedValue),
        int.Parse(ddlYears.SelectedValue),
        int.Parse(ddlMonths.SelectedValue),
        int.Parse(ddlDays.SelectedValue),
        int.Parse(ddlNumSCs.SelectedValue),
        txtDateFrom.Text,
        txtDateTo.Text,
        tradeGrades,
        executionGrades,
        int.Parse(txtMinProfitPips.Text),
        int.Parse(txtMaxProfitPips.Text));

    chart.DataBind();        
}       

I am calling the function with this command:

PopulateControl (chtEquityCurve, DB.GetAccountBalances());

I get this error in the intellisense: No overload for method 'GetAccountBalances' takes 0 arguments.

First, make a delegate type (typically I recomment people to use one of the available Func delegates, but they support only up to 16 input paramters, you have 18). Give it an appropriate name and define all the input parameters so they have the correct type and descriptive names. Make the delegate return an IEnumerable<T> :

public delegate IEnumerable<WhateverTypeIsReturned> GetDataSource(int firstParam, [...]);

Then, modify PopulateMyRepeatedControl so that it looks like this:

private void PopulateMyRepeatedControl(Chart chart, GetDataSource getDataSource)
{
    DBUtil DB = new DBUtil();
    DataTable symbols = GetSelectedSymbols();
    DataTable tradeGrades = GetSelectedTradeGrades();
    DataTable executionGrades = GetSelectedExecutionGrades();        

    chart.DataSource = getDataSource (
        int.Parse(txtStartBalance.Text),
        int.Parse(ddlTradeTypes.SelectedValue),
        ddlRepeatedTrades.SelectedValue,
        radSide.SelectedValue,
        ddlTradeSetups.SelectedValue,
        symbols,
        ddlChartTimeFrames.SelectedValue,
        int.Parse(ddlHours.SelectedValue),
        int.Parse(ddlYears.SelectedValue),
        int.Parse(ddlMonths.SelectedValue),
        int.Parse(ddlDays.SelectedValue),
        int.Parse(ddlNumSCs.SelectedValue),
        txtDateFrom.Text,
        txtDateTo.Text,
        tradeGrades,
        executionGrades,
        int.Parse(txtMinProfitPips.Text),
        int.Parse(txtMaxProfitPips.Text));

    chart.DataBind();
}

When you call the method, simply pass the chart and the method to use for collecting data:

PopulateMyRepeatedControl(oneChart, OneDataCollectionMethod);
PopulateMyRepeatedControl(anotherChart, AnotherDataCollectionMethod);

Of course, TheDataCollectionMethod must have the correct signature, otherwise the code will not compile.

Update
Regarding your update; note that you want to pass the method as an argument, not invoke it:

PopulateControl (chtEquityCurve, DB.GetAccountBalances);

Note that there are no paranthesis after the method name.

I think you should consider using some OOPs concepts rather going to parametrize the method name. As you have method which takes parameters which are actually the controls, think of separating this to different classes.

First you should think of re-factoring the code, because single method has too many parameters. You can encapsulate all the parameters to a class like

public class RepeatedCallParameters
{
   public string StartBalance{get;set;}

   ...

   ...
}

Then you can have a abstract class with all these parameters and a method PrepareRepeatedCallParameters()

public abstract class ChartProperties : Page
{
    protected abstract int StartBalance {get;}

    public RepeatedCallParameters PrepareRepeatedCallParameters()
    {
         RepeatedCallParameters p = new RepeatedCallParamters();
         p.StartBalance = StartBalance;
         return p;  
    }
}

Then

You can implement this class in the page

public class YourPage: ChartProperties
{
   protected override int StartBalance
   {
     get {return int.Parse(txtStartBalance.Text);} 
   }

   //All properties
   ..

   private void BindChartData()
   {
      RepeatedCallParameter p  = PrepareRepeatedCallParameters();
      Chart.DataSource = DB.RepeatedCall(p);
      Chart.DataBind();   
   }
}

This way you can have the code of populating the parameters in the single place ie in the abstract class.

类似下面的东西

PopulateMyRepeatedControl(Chart c , Func<List<List<T>>>  actionToBeCalled)  

Consider using delegates, which can be thought of as a type-safe function pointer. Delegates are made up of a delegate type and delegate instance. I would recommend using the built in generic delegate types Action and Func. Action has no return type, Func does.

public void MyMethod(Func<int, bool> predicate)
{
  if (predicate(5))
  {
    // true
  }
  else
  {
    // false
  }
}

// To call MyMethod, pass a delegate instance (here we are using a lambda expression)
MyMethod(x => x > 5);

The delegate instance gets called from MyMethod (as the predicate parameter), and because of the logic used, if the value is 5, false will be returned, otherwise any value greater than 5, true will be returned. You can see how the logic of the predicate is decoupled from the MyMethod method, which makes MyMethod reuseable for any piece of logic.

Note the last generic type used in Func is the return type, Action has no return types. Both delegate types take up to 16 parameters, but you probably want to think about your design if you need this many!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM