Пользовательские составные операторы в С#

Я хотел бы написать свои собственные составные операторы, которые имеют механизм, аналогичный механизмам using и lock, где они вводят код в начале и конце блока операторов перед компиляцией.

Я попытался найти вопросы, которые, возможно, задавали подобные вопросы, но я не мог правильно понять, как называется этот вид «области действия кода», кроме документации, в которой говорится, что это составные операторы.

Я знаю, что «блокировка» и «использование» являются ключевыми словами. Я не хочу иметь свои собственные ключевые слова, так как знаю, что это невозможно.

Я не уверен, что это возможно в С#, например:

Вместо того, чтобы делать:

StartContext(8);
//make method calls
EndContext();

Это может быть сокращено до:

DoSomethingInContext(8) {
    //make method calls
}

Конечно, это не обязательно должен быть просто однострочный вызов. Началом и концом инкапсулированного кода может быть несколько строк кода, которые вставляются пользовательским составным оператором.


person Gelion    schedule 19.02.2018    source источник
comment
В лучшем случае вы можете либо злоупотреблять IDisposable (хотя вопрос о том, является ли это злоупотреблением, зависит от вашего мнения), либо использовать делегаты, такие как DoSomethingInContext(8, () => { ... });   -  person Lasse V. Karlsen    schedule 19.02.2018


Ответы (2)


Вы можете немного переписать свой код:

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

Подпись для вашего метода будет примерно такой:

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

Одна вещь, о которой следует помнить, поскольку здесь есть альтернативный ответ, который использует IDisposable, заключается в том, что этот синтаксис на основе делегата может сделать intellisense в различных (более старых) версиях Visual Studio и ReSharper немного неуверенным. Иногда он пытается помочь вам заполнить параметр для DoSomethingInContext, когда вы действительно хотите, чтобы он помог вам заполнить параметры в вызовах методов внутри делегата. Это также верно для других IDE, таких как старые Xamarin Studios, у которых были серьезные проблемы с производительностью в отношении вложенных делегатов (если вы начнете вкладывать эти контекстно-зависимые вещи).

Я бы не изменил свой стиль программирования из-за этого, но имейте в виду.

person Lasse V. Karlsen    schedule 19.02.2018
comment
Из любопытства, при использовании этого метода инкапсулированный код разворачивается во время компиляции или сохраняется в переданном действии? - person Gelion; 19.02.2018
comment
Он переписывается как фактический метод либо для вызывающего типа, либо для отдельного класса, в зависимости от контекста, есть много мелких деталей. Однако он не встраивается в метод DoSomethingInContext; что-то передается, что ссылается на вызываемый метод. - person Lasse V. Karlsen; 19.02.2018

Если вы не возражаете против минимального количества дополнительного кода, вы можете повторно использовать оператор 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


Я использовал этот метод для объединения методов, которые необходимо использовать вместе, даже если что-то пойдет не так (например, методы Push и Pop для DrawingContext) — это сэкономит вам много блоков finally.

person Manfred Radlwimmer    schedule 19.02.2018
comment
Я могу понять этот механизм, хотя мне это кажется немного хакерским в злоупотреблении механизмом удаления. - person Gelion; 19.02.2018
comment
Этот шаблон используется в MVC @using Html.BeginForm(... - person Hans Kesting; 19.02.2018
comment
@Gelion До сих пор у меня это работало очень надежно. - person Manfred Radlwimmer; 19.02.2018
comment
Если подумать, я вижу, что на самом деле использую это для большего количества процессов, основанных на экземплярах, где мне потребуется множество вызовов методов (из которых не обязательно вызывать в том же порядке), выставленных на контекст, а не встроенный процесс так сказать. +1 за это @Manfred. - person Gelion; 19.02.2018
comment
Как я уже упоминал в своем комментарии к вопросу, это вопрос мнения о том, злоупотребляет ли это IDisposable или нет. Лично я думаю, что это нормально, в языке есть синтаксис, и было бы довольно глупо не использовать его, но многие люди зацикливаются на его стороне dispose-unmanaged-things. Лучше всего составить собственное мнение об этом. - person Lasse V. Karlsen; 19.02.2018
comment
Одна вещь, с которой вам нужно быть намного более дисциплинированной, — это исключения в методе Dispose, так как они эффективно затеняют любое исключение, которое возникает в части вызовов метода make внутри, это укусило меня в прошлом. Есть есть еще более хакерские способы справиться с этим, но я бы не советовал. По умолчанию вы должны убедиться, что в методе Dispose не может возникнуть никаких исключений. - person Lasse V. Karlsen; 19.02.2018