Подтвердить что ты не робот

Компилятор С# рассматривает выражение лямбда как общедоступный или закрытый метод?

Внутренне, компилятор должен переводить лямбда-выражения в методы. В таком случае эти методы будут закрытыми или общедоступными (или что-то еще), и можно ли это изменить?

4b9b3361

Ответ 1

Это зависит. В текущей версии Visual Studio методы, реализующие lambdas, никогда не являются общедоступными, но они не всегда являются частными. Простая программа для проверки некоторых версий lambdas:

public class Program
{
    public static void Main()
    {
        var program = new Program();
        Try("A", program.A);
        Try("B", program.B);
        Try("C", program.C);
        Console.ReadKey();
    }

    private static void Try(string name, Func<Action> generator)
    {
        var mi = generator().Method;
        Console.WriteLine($"{name}: DeclaringType={mi.DeclaringType}, Attributes={mi.Attributes}");
    }

    private Action A() => () => { };
    private Action B() => () => { ToString(); };
    private Action C()
    {
        var c = 1;
        return () => c.ToString();
    }
}

печатает

A: DeclaringType=Scratch.Program+<>c, Attributes=PrivateScope, Assembly, HideBySig
B: DeclaringType=Scratch.Program, Attributes=PrivateScope, Private, HideBySig
C: DeclaringType=Scratch.Program+<>c__DisplayClass4_0, Attributes=PrivateScope, Assembly, HideBySig

A У lambda нет никаких захватов. Он создан как метод internal для пустого класса замыкания.

B lambda захватывает this. Он создается как метод private содержащего класса.

C lambda захватывает C. Он создан как метод internal непустого класса замыкания.

Все это недокументировано и изменилось в прошлом, поэтому было бы неплохо не полагаться на него. Важно то, что когда вы вызываете анонимный метод, он ведет себя как указано. Если вам нужно что-то большее, вы не должны использовать анонимные методы. В зависимости от того, что вам нужно, вы также можете использовать lambdas, но с деревьями выражений, или вам может понадобиться создать обычные именованные методы.

Ответ 2

Внутри, компилятор должен переводить лямбда-выражения в методы.

Я предполагаю, что "лямбда" означает лямбду, преобразованную в тип делегата. Lambdas, преобразованные в типы дерева выражений, конечно, не генерируются как методы.

Компилятор действительно превращает такие лямбды в методы, да. Нет необходимости, чтобы он это делал, но делать это удобно.

В этом случае эти методы будут закрытыми или общедоступными (или что-то еще), и можно ли это изменить?

Вопрос несколько бессвязен. Предположим, я сказал вам, что лямбда является публичным методом. Он не имеет имени, доступного из С#; как бы вы воспользовались своей публичностью? Модификаторы доступности применимы к членам с именами. Само понятие домена доступности дает домен имени во время разрешения имени.

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

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

Ответ 3

Из CLR через книгу С# Джеффри Рихтера

Компилятор автоматически определяет новый закрытый метод в классе

... Компилятор автоматически создает имя метода

... анонимные методы, сгенерированные компилятором, всегда заканчиваются будучи приватным, и этот метод является либо статическим, либо нестатическим о том, обращается ли метод к любым членам экземпляра

Таким образом, метод объявляется как private или internal.

Например, код

class AClass {
    public void SomeMethod() {
        Action lambda = () => Console.WriteLine("Hello World");
        lambda();
    }
}

будет выдавать декларацию IL как

.field private static class [mscorlib]System.Action 'CS$<>9__CachedAnonymousMethodDelegate1'

Как вы можете видеть, это поле private static.

Однако заметьте, что выражение лямбда может быть оптимизировано, если вы измените пример на

class AClass
{
    string a = "Hello World";

    public void SomeMethod()
    {
        Action lambda = () => Console.WriteLine(a);
        lambda();
    }
}

компилятор будет оптимизировать его, и вообще не будет лямбда-декларации

IL_0001:  ldstr      "Hello World"
IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

Ответ 4

Как упоминалось в @hvd, разница между выражением лямбда использует параметры из окружающей среды (закрытие) или нет. Смотрите: Почему некоторые выражения лямбда С# компилируются для статических методов?

Таким образом, вопрос имеет смысл только для случая несоблюдения, когда выражение лямбда может быть преобразовано в оболочку делегата без каких-либо внешних зависимостей.

Вы можете передать этот сгенерированный класс (который в основном обертывает делегат), и он всегда будет ссылаться на сгенерированный делегат в определяющей сборке. Таким образом, вы можете вызывать его из любой точки, если на сборку ссылаются.

Просто проверьте, выполняется ли передача и выполнение действия, определенного в другой сборке, хотя сам Action.Method отмечен как внутренний.

// Main, first assembly
namespace ConsoleApplication1
{
    public class B : IB
    {
        Action _action;
        public void AddAction(Action act)
        {
            _action = act;
        }

        public void Invoke()
        {
            Console.WriteLine(_action.Target);
            Console.WriteLine("Is public: {0}", _action.Method.IsPublic);
            _action();
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            var a = new A();
            var b = new B();
            a.AddActionTo(b);
            b.Invoke();

            Console.ReadKey();
        }
    }
}

В другой сборке:

namespace OtherAssembly
{
    public interface IB
    {
        void AddAction(Action act);
    }

    public class A
    {
        public void AddActionTo(IB b)
        {
            Action act = () => { };
            b.AddAction(act);
        }
    }
}