10

我有以下情况。一些 .Net 运行时方法不能很好地工作,我需要制定一个解决方法。就像SqlCommand.ExecuteReader()有时返回一个封闭的阅读器对象一样,我想要这样的代码:

 SqlDataReader MyExecuteReader( this SqlCommand command )
 {
     var reader = command.ExecuteReader();
     if( reader.IsClosed() ) {
        throw new ClosedReaderReturnedException();
     }
     return reader;
 }

这会很好,除非我现在需要更改所有调用的代码,ExecuteReader()以便它现在调用MyExecuteReader()并且它使维护更加困难。

有没有办法以某种方式声明每当我的任何代码想要SqlCommand.ExecuteReader()调用时MyExecuteReader()都会被调用?是否可以有效地将现有方法替换为具有完全相同签名和相同名称的另一种方法?

4

4 回答 4

12

不,不支持您想要的。如果类不是密封的,并且方法不是静态的,您可以在不同的命名空间中继承同名的类并更改using, 并覆盖该方法。但这是一个有限的解决方案。

您最好的选择是使用不同的名称实现标准扩展方法,并替换所有用法。在大型代码库中,这似乎需要做很多工作,并且在未来可能容易出现人为错误——有人向原始方法添加了新的调用。但是,您的代码现在明确表明您已对行为进行了修改,这一事实抵消了一次性成本;您可以通过编写自己的自定义 FxCop 规则(或您定期运行的任何静态分析工具)来防止人为错误。

于 2013-01-09T06:43:54.343 回答
10

这类似于尝试使用模拟对代码进行单元测试时出现的问题。

一种解决方法是将SqlCommand代码中的使用替换为实现带有ExecuteReader方法的接口的对象。然后,您可以更轻松地替换对象,也许使用工厂模式。

所以你会替换这样的代码:

using (SqlCommand command = new SqlCommand(query))
{
    command.ExecuteReader();
}

和:

var sqlCommandFactory = new SqlCommandFactory();
using (ISqlCommand command = sqlCommandFactory.CreateSqlCommand(query))
{
    command.ExecuteReader();
}

首先定义一个包含您要替换的方法的接口:

public interface ISqlCommand
{
    SqlDataReader ExecuteReader();

    // further interface methods here...
}

然后创建一个工厂,它使用与构造函数相同的签名SqlCommand

internal class SqlCommandFactory
{
    bool _useMyClass = true;

    public ISqlCommand CreateSqlCommand(string query)
    {
        if (_useMyClass)
        {
            return new MySqlCommand(query);
        }
        else
        {
            return new SqlCommandWrapper(query);
        }
    }
}

然后,您在MySqlCommand课堂上编写替代代码:

public MySqlCommand : ISqlCommand
{
    public SqlDataReader ExecuteReader()
    {
        // your new code here
    }
}

由于 .NETSqlCommand类显然没有实现新ISqlCommand接口,因此创建一个包装类来执行此操作:

public SqlCommandWrapper : ISqlCommand
{
    SqlCommand _sqlCommand;

    public SqlCommandWrapper(string query)
    {
        _sqlCommand = new SqlCommand(query);
    }

    public SqlDataReader ExecuteReader()
    {
        _sqlCommand.ExecuteReader();
    }
}

一些额外的工作,但这种方法的好处是您可以将实现更改为您想要的任何内容,包括单元测试(通过将模拟工厂传递给您的代码)。

额外的工作应该是一次性的,并根据要求保留名称和原始方法签名。这应该使您的代码看起来更熟悉且更易于理解(与自定义/扩展方法相比),尤其是在您(或您的团队)习惯了这种众所周知的模式后。

于 2013-01-09T06:58:36.890 回答
3

好吧,您可以使用像 Cecil 这样的库来重写 IL,就像这里所做的那样:http: //plaureano.blogspot.dk/2011/05/introduction-to-il-rewriting-with-cecil.html ?m=1

但我确实相信重写你的代码会更好,所以其他任何可能阅读你的代码的人(以及以后的你自己)都清楚发生了什么:)

于 2013-01-09T06:57:19.420 回答
1

我不认为你想那样做。这会让所有阅读代码的人感到困惑。

但是,我相信它不能完成,但不能以任何好的或保证长期支持的方式完成。回到过去,我们可以注入函数而不是 DLL 导出的函数。防病毒程序使用了这种技术。

显然,有人设法弄清楚如何注入 .NET 方法。

请记住,这可能比它的价值要麻烦得多。您必须在安装了各种额外软件的各种平台上进行广泛的 QA(一种防病毒软件可能会破坏您的代码)。所以真的,只要按照其他人的建议去做——创建你的扩展方法,然后搜索并替换你的整个代码库。

于 2013-01-09T06:49:12.623 回答