【问题标题】:C# Repeating Functions with single line changes具有单行更改的 C# 重复函数
【发布时间】:2017-01-16 15:09:54
【问题描述】:

我注意到我一遍又一遍地重复相同的几行代码,我想重构以防止发生这种情况以进行维护。但是,每次迭代之间的更改都在所有验证的中间,因此我不确定完成此操作的最佳方法。我觉得有一些方法可以编写一个可以接受输入来更改内部代码的通用函数,但我能想到的唯一方法是每个枚举和一个 switch 语句。

有没有更好的方法让这段重复的代码更易读和维护?

在整个过程中重复的特定块:

    if (_read.HasRows)
    {
        while (_read.Read())
        {
            //DO SOMETHING...;
        }
    }
    _read.Close();

示例用法:

public List<object> TableList()
{
    List<object> list = new List<object>();

    _cmd.CommandText = "SELECT * FROM sys.tables";

    try
    {
        _read = _cmd.ExecuteReader();
    }
    catch (Exception e)
    {
                     //please ignore this try catch for now
        list[0] = e; //this is a temporary measure until I finalize error handling
    }

//block to generalize
    if (_read.HasRows)
    {
        while (_read.Read())
        {
            list.Add(_read.GetValue(0)); //unique line
        }
    }


    _read.Close();

    return list;
}

另一个例子:

private List<object[]> _Columns = new List<object[]>()    
private bool SetColumns()
{
    _oldCommand = _cmd.CommandText;
    _cmd.CommandText = "exec sp_columns " + tableName;
        try
        {
            _read = _cmd.ExecuteReader();
        }
        catch (Exception e)
        { }  //again, ignore the messy error handling.  That is coming next.


//block to generalize
    if (_read.HasRows)
    {
        while (_read.Read())
        {
            _Columns.Add(new object[2] { _read.GetValue(3), _read.GetValue(5) }); //unique line
        }
    }



    _read.Close();
    _cmd.CommandText = _oldCommand;
    if (_Columns.Count != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

只是在问题的具体范围底部提醒。 如何概括重复的代码块,同时仍然允许不同用途存在唯一行?我只举了两个例子,但这个问题在整个过程中有几个例子,我想在深入之前进行重构。

看到答案后的最终实现:

    /// <summary>
    /// Sets a list of tables in the connected database.
    /// </summary>
    /// <returns>Returns a List<string> of the tables.  If there is an error, then the first element of the list will conatin the exception.</returns>
    public List<string> TableList()
    {
        List<string> list = new List<string>();

        _cmd.CommandText = "SELECT * FROM sys.tables";

        ExecuteReader(_read => { list.Add(_read.GetString(0)); });

        _read.Close();

        return list;
    }


    /// <summary>
    /// sets column names and types to the _columns private variable [0] = name, [1] = type
    /// </summary>
    private void SetColumns()
    {
        _oldCommand = _cmd.CommandText;

        _cmd.CommandText = "exec sp_columns " + TableName;

        ExecuteReader(_read => columns.Add(new string[2] { _read.GetString(3), _read.GetString(5) }));

        _cmd.CommandText = _oldCommand;

        _read.Close();

    }


    /// <summary>
    /// Executes the reader with error handling and type returns.
    /// </summary>
    /// <param name="readline">the code that actually pulls the data from each line of the reader</param>
    private void ExecuteReader(Action<SqlDataReader> readline)
    {
        if (_con.State.HasFlag(ConnectionState.Closed)) { _con.Open(); }

        _read = _cmd.ExecuteReader();

        while (_read.Read())
        {
            readline(_read);
        }

        _con.Close();

    }

我选择了这种方法,因为它在我的经验水平上感觉最易读。正如许多其他人所推荐的那样,我已经对 Dapper 进行了一些研究。我很可能会在未来的项目中使用它,因为出于我自己的经验,我喜欢至少编写一次自己的样板代码。

感谢大家的精彩回答。我希望将来其他人会发现这对您有所帮助。

【问题讨论】:

  • 您可能想看看Dapper,因为它负责处理与 ADO.Net 相关的大量样板代码。
  • 您想将示例 1 与示例 2 统一起来,还是希望将它们中的每一个都概括起来?
  • 感谢@stuartd 的评论我通常对验证有点疯狂,不会花时间看看它们是否有必要。像这样的改变正是我试图概括的原因,这样我就可以做出这些改变而不必改变 100 件事。
  • @Ghasan 我想统一这些例子。 if (read_.HasRows) 块和其他非常相似的示例重复了太多次。在第三次复制粘贴后,我知道我做错了什么。到目前为止,lamba 表达式的答案是正确的。

标签: c# function lambda refactoring


【解决方案1】:

您可以通过传入 lambda 表达式来分解填充代码,例如:

SetColumns(reader =&gt; _Columns.Add(new object[2] { _read.GetValue(3), _read.GetValue(5) }));

SetColumns(reader =&gt; list.Add(reader.GetValue(0)));

查看 while 循环内的更新行:

private List<object[]> _Columns = new List<object[]>();
private bool SetColumns(Action<DbDataReader> populateColumns)
{
    _oldCommand = _cmd.CommandText;
    _cmd.CommandText = "exec sp_columns " + tableName;
        try
        {
            _read = _cmd.ExecuteReader();
        }
        catch (Exception e)
        { }  //again, ignore the messy error handling.  That is coming next.


//block to generalize
    if (_read.HasRows)
    {
        while (_read.Read())
        {
            populateColumns(_read);
        }
    }



    _read.Close();
    _cmd.CommandText = _oldCommand;
    if (_Columns.Count != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

【讨论】:

  • 我不完全理解populateColumns(_read) 表达式。 Lamba 表达式是否会自动填充该对象? Lamba 表达式返回什么,DbDataReaderobject[2]
  • 在这种情况下,lambda 表达式不返回任何内容(因为它是一个 Action),而是将 DbDataReader 作为参数。当你执行populateColumns(_read).你传入的 lambda 表达式成为一个匿名函数,分配给参数populateColumns。每次在 while 循环中调用 populateColumns 时,它都会以 _read 作为参数值来执行该函数。在MSDN 上有一个关于如何使用 lambda 表达式的非常详尽的解释。
  • 我一定会再次阅读它们。肯定不是第一次点击,但是看到大家的回答和cmets,确实是说的更清楚了。我花了很多时间使用专有脚本语言。学习一门更强大、更底层的语言比我想的要慢。
【解决方案2】:

当您考虑重用时,脑海中有两种方法。

  • 只需将其作为一种方法,即可在任何地方使用。
  • 继承

现在在这种情况下,继承听起来有点咄咄逼人。但是制作一个方法是正确的。

这是我的镜头:

public class FillerFromDb
{
    public IList ListToBeFilled { get; set; }
    SqlCommand Command { get; set; }
    SqlDataReader Reader { get; set; }
    public FillerFromDb ( IList listToBeFilled, SqlCommand commandToRun )
    {
        ListToBeFilled = listToBeFilled;
        Command = commandToRun;
    }

    public void RunFor ( Func<SqlDataReader, object> rowProcessing )
    {
        ReadFromDb();
        ProcessRow(rowProcessing);
    }

    private void ReadFromDb ()
    {
        try
        {
            Reader = Command.ExecuteReader();
        }
        catch ( Exception e )
        {
            // handle the exception
        }
    }

    private void ProcessRow ( Func<SqlDataReader, object> rowProcessing )
    {
        if ( Reader.HasRows )
        {
            while ( Reader.Read() )
            {
                var result = rowProcessing(Reader);
                ListToBeFilled.Add(result);
            }
        }

        Reader.Close();
    }
}

和被调用者:

var filler = new FillerFromDb(list/*or _Columns*/, _cmd);

filler.RunFor(row => row.GetValue(0));

如果您要进行重构,将工作函数封装在这样的类中,您会感觉更好。

【讨论】:

  • 只是想澄清一下。您的方法中的ProcessColumns 是否故意与processcolumns 不同?我对这些 lambda 表达式的变量/类型处理相对不清楚。
  • @MikeyAwbrey 在我看来像是一个错字,它应该与参数名称匹配。这里的想法是将“不同位”作为要在相关点执行的函数传入。
【解决方案3】:

试试这个:

private void CommonBlock(Action act)
    {
        // some code
        if (act!=null) act.Invoke();
        //next code
    }

使用具体代码调用:

CommonBlock(()=>{Debug.WriteLine("you action");/*Anything you want to do*/});

【讨论】:

  • 这很清楚,但是act.Invoke() 会完全按照书面说明工作,还是我需要提供更多信息(类型、变量名等)?如果我在some code 的范围内声明了一个变量,我可以将它从另一个方法传递给Action 对象吗?
  • 如果某些代码(var 等)在您的旧代码中可用,那么它在 CommonBlock() 中可用。因此,您可以将它们传递给操作。而且您不需要更多信息。只需将 debug.writeline 更改为您的代码(例如 list.Add(_read.GetValue(0)); )
【解决方案4】:

最简单的方法是创建一个扩展方法:

public static class Extensions
{
  public static IDataReader Read(this IDataReader reader, Action<IDataReader> action)
  {
    if(reader.HasRows)
    {
      while(reader.Read())
      {
        action(reader);
      }
    }
  }
}

使用您的原始样本的示例如下:

public List<object> TableList()
{
    List<object> list = new List<object>();

    _cmd.CommandText = "SELECT * FROM sys.tables";

    try
    {
        _read = _cmd.ExecuteReader();
    }
    catch (Exception e)
    {
                     //please ignore this try catch for now
        list[0] = e; //this is a temporary measure until I finalize error handling
    }

    _read.Read(r => list.Add(r.GetValue(0)));        
    _read.Close();

    return list;
}

【讨论】:

  • 我喜欢将其作为扩展实现的方法。看起来很干净。如果我不注意的话,这可能会产生一些混乱。我认为我的 API 知识不够深入,无法在 6 个月后将其视为扩展(除非我非常明显地注意到了扩展类)。
  • 扩展方法是最良性的方法。如果您向下看,我会关注 Dapper 及其相关项目(例如 Extensions/Extensions.Linq)
  • @juharr 之前提到过 Dapper。我已经简要地看了一下。我确实相信在未来我会使用更多类似的实际应用程序,但我发现一旦自己完成样板文件可以帮助我真正了解我做得更好的地方。感谢您的推荐和回答。
【解决方案5】:

通过 Action 或 Func 将差异传递给函数。编写一个通用版本,然后传入要执行的不同版本。

【讨论】:

  • 其他人已经很好地介绍了使用动作的方法,但是您能否在此示例的上下文中澄清一下 Func 的用法?我正在阅读他们的 MSDN 页面,但我不确定如何将其实际应用到此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-24
  • 1970-01-01
  • 1970-01-01
  • 2013-04-14
  • 1970-01-01
相关资源
最近更新 更多