C#中的自定义复合语句

7
我想编写自己的定制复合语句,其机制类似于usinglock机制,其中它们在编译之前在语句块的开头和结尾注入代码。
我已经尝试搜索了解是否有类似问题的人提出过类似的问题,但我无法正确地弄清这种“代码作用域”被称为什么,除了文档说这些是复合语句之外。
我知道“lock”和“using”是关键字。我不想有自己的关键字,因为我知道这是不可能的。
例如,在C#中是否可能做到这一点:
与其进行以下操作:
StartContext(8);
//make method calls
EndContext();

这可以简化为:
DoSomethingInContext(8) {
    //make method calls
}

当然,这不一定只是一个单行调用。封装代码的开始和结束可以是多行代码,这些代码将被插入自定义复合语句中。

1
你最好的选择是要么滥用 IDisposable(尽管这是否为滥用是一个观点问题),要么使用委托,例如 DoSomethingInContext(8, () => { ... }); - Lasse V. Karlsen
2个回答

8
您可以稍微改写您的代码:

您可以稍微修改您的代码:

DoSomethingInContext(8, () => {
    // make method calls
});

您的方法的签名应该是这样的:

public void DoSomethingInContext(int contextId, Action contextBoundAction)
{
    // start/open/enter context
    try
    {
        contextBoundAction();
    }
    finally
    {
        // stop/close/exit context
    }
}

需要注意的一点是,由于这里有一个使用IDisposable的替代答案,所以基于委托的语法可能会导致在各种(旧版)Visual Studio和ReSharper中的智能感知出现一些问题。有时候它试图帮助你填写DoSomethingInContext的参数,但实际上你想让它帮助你填写委托内方法调用的参数。这对其他IDE也适用,例如旧版Xamarin Studios,在嵌套委托(如果您开始嵌套这些上下文绑定的东西)方面存在严重的性能问题。

我不会因此更改我的编程风格,但要注意这一点。


出于好奇,使用这种技术时,封装的代码是否会在编译期间被解开,还是会保留在传递进来的 Action 中? - Gelion
它会被重写为实际的方法,可以是调用类型或一个独立的类,具体取决于上下文,有很多细节需要处理。但它不会嵌入到DoSomethingInContext方法中;相反,会传递一个引用要调用的方法。 - Lasse V. Karlsen

6

如果您不介意增加少量的代码,可以重复使用using语句。只需将包装器代码放置在构造函数和Dispose方法中即可,例如:

public class MyWrapper: IDisposable
{
    int _id;

    public MyWrapper(int id)
    {
        _id = id;
        Debug.WriteLine("Begin " + _id);
    }

    public void Dispose()
    {
        Debug.WriteLine("End " + _id);
    }
}

使用方法:

using(new MyWrapper(id))
{
    Debug.WriteLine("Middle " + id);
}

DotNetFiddle 上的演示


我使用这种方法来封装需要一起使用的方法,即使出现问题(例如 DrawingContextPushPop 方法)- 这样可以避免很多 finally 块。


我能理解这个机制,尽管对我来说,这似乎有点“hacky”,滥用了处理机制。 - Gelion
这个模式用于MVC中的@using Html.BeginForm(... - Hans Kesting
@Gelion 到目前为止,它对我来说非常可靠。 - Manfred Radlwimmer
说起来,我可以想象自己会在更多基于实例的过程中使用它,其中我需要暴露给上下文的方法调用的数量可能不一定按相同的顺序调用,而不是所谓的“内联”过程。为此@Manfred加1。 - Gelion
1
正如我在评论中提到的那样,这是一个关于是否滥用 IDisposable 的观点问题。就我个人而言,我认为这是可以接受的,因为语言中有相应的语法,不去利用它反而有些愚蠢。但很多人会对释放非托管资源这一方面感到困惑。最好自己做出判断。 - Lasse V. Karlsen
你需要更加严格地处理 Dispose 方法中的异常,因为这些异常会有效地掩盖在其中进行“方法调用”部分中发生的任何异常,这曾经让我吃过亏。虽然还有更多的权宜之计可以解决这个问题,但我不建议采用。你应该默认情况下确保 Dispose 方法中不会发生任何异常。 - Lasse V. Karlsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接