Nano Hash - криптовалюты, майнинг, программирование

Использование делегатов в C #

Не могли бы вы помочь мне в понимании делегатов в языке C # и .NET Framework? Я пытался проверить какой-то код и обнаружил, что полученные мной результаты были для меня неожиданными. Вот:

class Program
{
    public static int I = 0;

    static Func<string> del = new Func<string>(I.ToString);

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del());
    }
}

Ответ был 0, но не 10. Почему?

28.11.2012

  • Я думаю, ты забыл () после ToString 28.11.2012
  • @Rotem: Нет, он этого не сделал. 28.11.2012
  • @Rotem - это объявление делегата. Добавление () вызовет ToString. 28.11.2012
  • Извините, никогда не использовал Funcs, было предположением :) 28.11.2012
  • Кстати, я также пытался использовать нестатическое поле и делегат, но результат был таким же. 28.11.2012
  • +1 за хороший вопрос, хорошо заданный. Отличный пример того, как, казалось бы, простой вопрос может выделить плохо понятную область языка / платформы. 28.11.2012
  • Экземпляр делегата (одноадресной рассылки) может указывать либо на метод экземпляра, либо на метод static. Когда он представляет метод экземпляра, делегат содержит как целевой объект, для которого вызывается метод, так и информацию о методе. Поэтому, когда вы говорите del = I.ToString;, del будет содержать объект I, который здесь является Int32 (неизменяемый тип значения). Когда вы используете анонимную функцию del = () => I.ToString();, компилятор создает метод static string xxx() { return I.ToString(); }, а объект del содержит этот сгенерированный метод. 28.11.2012
  • (замечание к предыдущему комментарию) На самом деле, после проверки del.Method и del.Target, похоже, что компилятор генерирует из () => I.ToString() как вложенный тип внутри вашего Program класса, так и нестатический метод этого вложенного типа. У обоих странные имена. Но это всего лишь деталь. 28.11.2012
  • @JeppeStigNielsen: Большое спасибо! Думаю, теперь мне все ясно. 28.11.2012
  • @JeppeStigNielsen: Тот факт, что он создает вложенный тип, является деталью реализации, которая могла уже измениться хотя бы один раз. В моем примере с использованием .NET 4 он просто создал статический метод в типе, который содержит делегат. 28.11.2012
  • @DanielHilgarth Ты прав. На самом деле это так, как я думал в своем первом комментарии здесь. Я сделал ошибку при тестировании, потому что я захватил локальную переменную i в объекте делегата, который я сделал внутри методом. Конечно, это что-то другое. После этого локальная переменная не так уж и локальна ... Так что я случайно усложнил ситуацию, чем в исходной настройке. Так что возможно эта деталь не изменилась, я просто ошибся. 28.11.2012
  • @JeppeStigNielsen: Да, в этом есть смысл. Для захвата локальной переменной потребовалось бы создать анонимный класс, иначе не было бы места для его хранения. 28.11.2012

Ответы:


1

Причина в следующем:

То, как вы объявляете делегата, указывает непосредственно на метод ToString статического экземпляра int. Он зафиксирован во время создания.

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

В этом случае, очевидно, что выполняемый метод - это метод ToString. Интересной частью является экземпляр, на котором выполняется метод: это экземпляр I во время создания, что означает, что делегат не использует I, чтобы заставить экземпляр использовать, но сохраняет ссылку на сам экземпляр.

Позже вы измените I на другое значение, в основном присвоив ему новый экземпляр. Это не меняет волшебным образом экземпляр, захваченный в вашем делегате, зачем это нужно?

Чтобы получить ожидаемый результат, вам нужно будет изменить делегата на это:

static Func<string> del = new Func<string>(() => I.ToString());

Таким образом, делегат указывает на анонимный метод, который выполняет ToString на текущем I во время выполнения делегата.

В этом случае выполняемый метод - это анонимный метод, созданный в классе, в котором объявлен делегат. Экземпляр имеет значение NULL, поскольку это статический метод.

Взгляните на код, который компилятор генерирует для второй версии делегата:

private static Func<string> del = new Func<string>(UserQuery.<.cctor>b__0);
private static string cctor>b__0()
{
    return UserQuery.I.ToString();
}

Как видите, это нормальный метод, который что-то делает. В нашем случае он возвращает результат вызова ToString в текущем экземпляре I.

28.11.2012
  • Я пишу, думая, что static Func<string> del = new Func<string>(I.ToString); сделает бокс I, а del всегда будет ссылаться на метод ToSting того времени I? 28.11.2012
  • Интересно, и это может привести к некоторому странному поведению сборщика мусора, если вы не избавляетесь от своих делегатов должным образом :) 28.11.2012
  • Я думал о боксе, потому что я также пробовал пример с static Func<string> del = new Func<string>(Program.GetI), где Program.GetI - статическая функция, которая возвращает статический I. И результат был 10. Поэтому сейчас, на мой взгляд, связывание делегата с I.ToString приводит к боксу I, а использование анонимного метода также приводит к боксу, но только в том месте, где я вызываю делегат этим анонимным методом. Это правильно? Или мне еще раз прочитать свои книги? :)) 28.11.2012
  • @ user1859587: Упаковка - это процесс создания экземпляра ссылочного типа из экземпляра типа значения. 28.11.2012
  • @ user1859587: Я провел несколько тестов, и результаты одинаковы вне зависимости от того, есть ли у нас unboxed int, boxed int и Nullable ‹int›, и независимо от того, статические они или нет (хотя с использованием компилятора Roslyn). 28.11.2012
  • @flindeberg: вы даже можете использовать свой собственный класс вместо int. Он по-прежнему будет вести себя так же, потому что основная причина не изменится: делегат указывает на конкретное воплощение ToString на одном конкретном объекте. Неважно, ссылочный ли это тип или тип значения. 28.11.2012
  • Извините, но почему использование анонимного метода не приведет к тому же? Связывает ли делегат с функцией также его с конкретным воплощением метода для одного конкретного объекта? Означает ли это, что когда я связываю делегат с некоторым методом объекта и изменяю этот объект, делегат будет ссылаться на старый метод? 28.11.2012
  • @DanielHilgarth: Точно, я сам не знал, как будет обрабатываться бокс / распаковка, поэтому мне пришлось проверить сам :) 28.11.2012
  • @ user1859587: у делегата есть метод и цель (экземпляр), имеет значение, захватывает ли он ваш экземпляр или экземпляр лямбда-функции, в свою очередь, содержит ссылки на экземпляр. 28.11.2012
  • @flindeberg: Спасибо, теперь я это вижу :) 28.11.2012
  • @DanielHilgarth: когда становится целью делегата, кажется, что коробочная версия int сохраняется как цель (что объясняет поведение, поскольку, если он ссылается на хранящийся в стеке неупакованный int, значение изменится). 28.11.2012
  • @flindeberg: Похоже на то. IL поддерживает это. 28.11.2012
  • @ user1859587: Пожалуйста :) Обязательно примите и ответ Дэниэлса :) 28.11.2012
  • @DanielHilgarth: Спасибо за терпение и за помощь! 28.11.2012
  • @ user1859587: Пожалуйста. Кстати: я попытался обновить ответ, чтобы было немного понятнее, что здесь происходит. Вы можете перечитать его :-) 28.11.2012
  • Дэниел, просто чтобы подтвердить комментарий Флиндеберга: ваш ответ правильный, а ваши комментарии относительно бокса - нет. user1859587 правильно: наблюдаемое поведение является следствием того, что делегат перехватывает получателя вызова. Хотя получатель вызова ToString для int будет ссылкой на переменную int, делегат не имеет возможности поместить ссылку на переменную int в куче; ссылки на переменные могут переходить только во временное хранилище. Таким образом, он делает следующее лучшее: он помещает int и делает ссылку на это место в куче. 28.11.2012
  • @EricLippert: Спасибо за разъяснения. В своих комментариях о боксе и захвате я хотел сказать, что поведение OP не имеет ничего общего с тем фактом, что I является типом значения. И это утверждение должно быть правильным. 28.11.2012
  • Это правильно. Делегат фиксирует значение, а не переменную. 28.11.2012
  • Интересным следствием того факта, что получатель упакован в коробку, является то, что невозможно создать делегат GetValueOrDefault () для обнуляемого int, поскольку упаковка с обнуляемым int создает упакованный int, а не упакованный в коробку int, допускающий значение NULL, а упакованный в коробку int имеет нет метода GetValueOrDefault (). 28.11.2012
  • @EricLippert: Очень интересно. Для этого есть даже специальное сообщение компилятора: Невозможно привязать делегат к 'System.Nullable ‹int› .GetValueOrDefault ()', потому что он является членом 'System.Nullable ‹T›' 28.11.2012
  • Вот почему мы никогда не увидим запутанного соревнования C # - делегаты сделают это слишком легко. 28.11.2012

  • 2

    Вам необходимо передать I вашей функции, чтобы I.ToString() мог быть выполнен в подходящее время (а не во время создания функции).

    class Program
    {
        public static int I = 0;
    
        static Func<int, string> del = num => num.ToString();
    
        static void Main(string[] args)
        {
            I = 10;
            Console.WriteLine("{0}", del(I));
        }
    }
    
    28.11.2012

    3

    Вот как это нужно делать:

    using System;
    
    namespace ConsoleApplication1
    {
    
        class Program
        {
            public static int I = 0;
    
            static Func<string> del = new Func<string>(() => {
                return I.ToString();
            });
    
            static void Main(string[] args)
            {
                I = 10;
                Console.WriteLine("{0}", del());
            }
        }
    }
    
    28.11.2012

    4

    Делегат C # enable инкапсулирует как объект, так и экземпляр и метод. Объявление делегата определяет класс, производный от класса System.Delegate. Экземпляр делегата инкапсулирует список вызовов, который представляет собой список из одного или нескольких методов, каждый из которых называется вызываемой сущностью.

    узнать больше формы

    http://asp-net-by-parijat.blogspot.in/2015/08/what-is-delegates-in-c-how-to-declare.html

    24.08.2015

    5

    Я предполагаю, что int передаются значениями, а не ссылками, и по этой причине при создании делегата он является делегатом метода ToString текущего значения «I» (0).

    28.11.2012
  • Ваше предположение неверно. Это не имеет ничего общего с типами значений и ссылочными типами. То же самое произойдет и со ссылочными типами. 28.11.2012
  • На самом деле это так, если, например, мы используем экземпляр класса, а ToString манипулирует данными экземпляра для получения возвращаемого значения, он будет выдавать возвращаемое значение текущего состояния класса, а не состояние класса, когда был создан делегат. Функция не запускается, когда делегат создан, и есть только один экземпляр класса. 28.11.2012
  • Func ‹string› (() = ›I.ToString ()) Также должно работать, потому что мы не используем I до вызова метода. 28.11.2012
  • Но это не эквивалент того, что здесь происходит. Если бы вы использовали класс Foo вместо int и изменили строку I = 10 на I = new Foo(10), вы бы получили тот же результат, что и с текущим кодом. I.Value = 10 - это совсем другое. Это не назначает новый экземпляр I. Но здесь важно назначить новый экземпляр I. 28.11.2012
  • Да, этот делегат работает, как я уже показал в своем ответе задолго до того, как вы опубликовали свой. 28.11.2012
  • Хорошо, проблема в том, чтобы переназначить I, если бы я был изменяемым и мы изменили бы объект без переназначения I, это бы сработало. в этом примере мы не можем этого сделать, потому что I - int (неизменяемый). 28.11.2012
  • Новые материалы

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

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..


    © 2024 nano-hash.ru, Nano Hash - криптовалюты, майнинг, программирование