Страницы

Поиск по вопросам

Показаны сообщения с ярлыком делегаты. Показать все сообщения
Показаны сообщения с ярлыком делегаты. Показать все сообщения

пятница, 28 февраля 2020 г.

C# делегаты и анонимные функции

#c_sharp #делегаты #анонимная_функция


Имеется следующий блок кода : 

delegate int StrMod(int a);
static void main()
{
StrMod strmod = delegate{return 10;};
Console.WriteLine(strmod(5));
}


Консоль:


  10


Почему на 4 строку компилятор не ругается? Ведь входные параметры анонимной функции
не соответствуют параметрам делегата.
    


Ответы

Ответ 1



Вы здесь объявляете делегат с помощью анонимного метода. StrMod strmod = delegate{return 10;}; Согласно документации об анонимных методах, у анонимного метода допускается опускать список параметров, если они вам не нужны внутри метода(в отличии от лямбд выражений, где список параметров нужно указывать всегда явно). В этом случае, анонимный метод может быть преобразован к делегату с различной сигнатурой. Anonymous methods enable you to omit the parameter list. This means that an anonymous method can be converted to delegates with a variety of signatures. Поэтому этот код компилируется успешно.

понедельник, 24 февраля 2020 г.

Ко/контрвариантность у делегата

#c_sharp #делегаты


Есть такой делегат

delegate void Del(string z);


И ему присваивается метод 

public void Foo(object z)
{

}


Присваивание происходит нормально, за счет вариантности. Однако если в параметре
делегата стринг поменять на int тогда компилятор уже возмущается. Почему ? Полагаю,
что это как-то связано с ссылочным/структурным типом. Но ведь все они идут от object
    


Ответы

Ответ 1



Вы правы, вариантность делегатов ограничена ссылочными типами. Вот нормативная цитата из C# language reference (лежит в \VC#\Specifications\1033\CSharp Language Specification.docx), раздел 15.2 (перевод мой): 15.2 Совместимость делегатов Метод или делегат M называется совместимым с делегатным типом D, если выполняются следующие условия: У D и M одинаковое количество параметров, и каждый параметр в D имеет одинаковые модификаторы ref/out, как и соответствующий параметр в M. Для каждого параметра, не содержащего ref/out, существует тождественное преобразование (§6.1.1) или неявное ссылочное преобразование (§6.1.6) из типа параметра в D в тип соответствующего параметра в M. Для каждого ref-/out-параметра, тип параметра в D в точности тот же, что и в M. Существует тождественное или неявное ссылочное преобразование из возвращаемого типа в M к возвращаемому типу в D. Это значит, что типы параметром должны быть либо одинаковы (с точностью до разницы между object и dynamic), либо тип параметра должен быть ссылочным подтипом типа параметра в D. Это исключает значимые типы.

Групповые делегаты

#c_sharp #делегаты


Action a1 = () => Console.Write(1); 
Action a2 = () => Console.Write(2); 
Action a3 = () => Console.Write(3); 
((a1 + a2 + a3) - (a1 + a2))(); 
((a1 + a2 + a3) - (a1 + a3))();


Увидел данный пример и стало интересно. Как в данном случае отрабатывает удаление
делегата. Что происходит при (a1 + a2) и (a1 + a3), и почему первый вариант удаляет
как надо а второй не срабатывает вовсе?
    


Ответы

Ответ 1



Пускай a1 + a2 + a3 превращается в делегат, который условно обозначим [a1, a2, a3], a1 + a2 -- [a1, a2], a1 + a3 -- [a1, a3]. При вычитании в [a1, a2, a3] ищется кусок подряд, который равен вычитаемому. Для [a1, a2] такой кусок найдётся, начиная с индекса 0, а для [a1, a3] -- нет. Поэтому при вычитании [a1, a2, a3] - [a1, a2] получается [a3], а при вычитании [a1, a2, a3] - [a1, a3] подходящий кусок не находится, и вычитание не происходит. Смысл такого правила в том, что если уж делегат получен сложением одного списка с другими, то это первый список будет найден в суммарном делегате как подсписок. Поэтому вычитание в этом случае имеет смысл. Цитата из стандарта, параграф 7.8.5: ... Результатом является новый список вызова, состоящий из списка первого операнда с удалёнными элементами списка второго операнда, при условии, что второй список является непрерывным (нетривиальным) подсписком первого [...] В противном случае, результатом является вычитаемое.

воскресенье, 16 февраля 2020 г.

Групповые делегаты

#c_sharp #делегаты


Action a1 = () => Console.Write(1); 
Action a2 = () => Console.Write(2); 
Action a3 = () => Console.Write(3); 
((a1 + a2 + a3) - (a1 + a2))(); 
((a1 + a2 + a3) - (a1 + a3))();


Увидел данный пример и стало интересно. Как в данном случае отрабатывает удаление
делегата. Что происходит при (a1 + a2) и (a1 + a3), и почему первый вариант удаляет
как надо а второй не срабатывает вовсе?
    


Ответы

Ответ 1



Пускай a1 + a2 + a3 превращается в делегат, который условно обозначим [a1, a2, a3], a1 + a2 -- [a1, a2], a1 + a3 -- [a1, a3]. При вычитании в [a1, a2, a3] ищется кусок подряд, который равен вычитаемому. Для [a1, a2] такой кусок найдётся, начиная с индекса 0, а для [a1, a3] -- нет. Поэтому при вычитании [a1, a2, a3] - [a1, a2] получается [a3], а при вычитании [a1, a2, a3] - [a1, a3] подходящий кусок не находится, и вычитание не происходит. Смысл такого правила в том, что если уж делегат получен сложением одного списка с другими, то это первый список будет найден в суммарном делегате как подсписок. Поэтому вычитание в этом случае имеет смысл. Цитата из стандарта, параграф 7.8.5: ... Результатом является новый список вызова, состоящий из списка первого операнда с удалёнными элементами списка второго операнда, при условии, что второй список является непрерывным (нетривиальным) подсписком первого [...] В противном случае, результатом является вычитаемое.

среда, 5 февраля 2020 г.

Разница между Invoke и обычным вызовом делегата через ()

#c_sharp #делегаты


В чём разница между Invoke и обычным вызовом делегата через () в C#?
    


Ответы

Ответ 1



Для делегатов разницы между вызовом через d.Invoke(список аргументов) и d(список аргументов) нет. При этом генерируется одинаковый IL-код: вызов через скобки транслируется в Invoke. (Проверка.) Интересное синтаксическое отличие состоит в том, что проверка на null с Invoke-синтаксисом проще: d?.Invoke(список аргументов). Документация: Common Patterns for Delegates / Handling Null Delegates. Также для вызова через рефлексию вам понадобится использовать Invoke, т. к. скобки на уровне рефлексии никак не представлены.

вторник, 28 января 2020 г.

Когда обычно нужно использовать делегаты?

#c_sharp #делегаты


Изучаю C#, прошу разъяснить про делегаты - которые предназначены для ситуаций, когда
требуется передать методы другим методам в качестве параметров! Но кода это надо? И зачем? 
Большое спасибо!
    


Ответы

Ответ 1



Реализация обработчиков событий и событийной модели как таковой. Скажем, кликаете на кнопку, и при клике что-то происходит. Чтобы сообщить программе, какое именно "что-то" должно произойти, применяют делегаты LINQ и любые другие элементы функционального программирования в C# - там все основано на делегатах и их "сокращенных" вариантах - лямбда-функциях Они также весьма полезны при реализации многопоточных и асинхронных программ Вообще для реализации коллбэков. Например, функций сортировки. Чтобы сообщить программе, как именно нужно сортировать пользовательские типы данных, в метод сортировки необходимо передать другой метод, позволяющий сравнивать экземпляры сортируемого типа А вообще очень любопытно, по какому источнику вы изучаете язык, что в нем этого не написано?

среда, 22 января 2020 г.

C# Короткая запись метода с помощью анонимного делегата

#c_sharp #делегаты


Подскажите пожалуйста, как реализовать 
Вот исходный код

vm.RegisterCommand('+', v => Inc(v));
private static void Inc(IVirtualMachine v)
{
    if (v.Memory[v.MemoryPointer] == (char)255)
        v.Memory[v.MemoryPointer] = (char)0;
    else
        v.Memory[v.MemoryPointer]++;
}


Хочу заменить на что то вроде 

vm.RegisterCommand('+', b => (b.Memory[b.MemoryPointer] == 255) ? 1 : 2 );


на 1:2 не обращайте внимания это для сокращения кода
    


Ответы

Ответ 1



Ну у вас по сути сложение по модулю, поэтому можно записать вместо if что-то вроде v.Memory[v.MemoryPointer] = (char)((v.Memory[v.MemoryPointer] + 1) % 256), ну и, соответственно, в виде лямбды: vm.RegisterCommand('+', v => v.Memory[v.MemoryPointer] = (char)((v.Memory[v.MemoryPointer] + 1) % 256)); Если IVirtualMachine.Memory – это реально byte-массив, то код можно сократить: vm.RegisterCommand('+', v => v.Memory[v.MemoryPointer]++)); Если вы хотите переписать свой метод дословно, то это будет выглядеть так: vm.RegisterCommand('+', v => { if (v.Memory[v.MemoryPointer] == (char)255) v.Memory[v.MemoryPointer] = (char)0; else v.Memory[v.MemoryPointer]++; }); Но это не улучшает читаемость, а даже скорее наоборот. А с использованием тернарного оператора, например, так: vm.RegisterCommand('+', v => v.Memory[v.MemoryPointer] = (char)(v.Memory[v.MemoryPointer] == 255 ? 0 : (v.Memory[v.MemoryPointer] + 1) ));

понедельник, 6 января 2020 г.

c# сделать функцию параметром функции

#c_sharp #aspnet #функции #делегаты #параметры


Есть множество функций типа flipImageV. До и после кода в этих функциях должны быть
одинаковые строки кода. Что бы в каждой функции типа flipImageV их не писать, хочу
сделать одну, куда буду передавать нужную функцию на выполнение...

Вот универсальная функция, куда передаю выбранную функцию типа flipImageV:

protected void editImage(delegate func) {
        currentImage.ImageUrl = @"..\images\img0.jpg";
        func();
        photo.Save(@"~\" + imagePath);
    }


Функция типа flipImageV:

protected void flipImageV(object sender, EventArgs e) {
        photo.FlipVertical();
    }


Использование того шаблона: 

editImage(flipImageV);


или

protected void flipImageV(object sender, EventArgs e) {
        editImage(photo.FlipVertical());
    }


Что должно быть вместо delegate func? Как это лучше сделать?
    


Ответы

Ответ 1



Используйте просто Action: protected void editImage(Action func) { currentImage.ImageUrl = @"..\images\img0.jpg"; func(); photo.Save(@"~\" + imagePath); } Вызывать как-то так: editImage(() => photo.FlipVertical()); Если вам нужно непременно передавать внутрь функцию наподобие flipImageV, которая получает два игнорируемых аргумента, можно так: editImage(() => flipImageV(null, EventArgs.Empty));

среда, 1 января 2020 г.

Рефлексия в c#. Как получить исходный код метода?

#c_sharp #рефлексия #делегаты


Как получить исходный код метода? У меня есть функция которая принимает делегат Predicate
и мне нужно вывести исходный код функции, которую туда передали.
Сама функция

public string GetSource(Predicate match) {
  Console.WriteLine("Source method");
}


Применение этой функции

GetSource(x => x.Name == "name");


Результат вызова функции в консоли: x.Name == "name"
    


Ответы

Ответ 1



Похожие вещи делаются в C# через так называемые деревья выражений. Эта тема сложная, которая требует ознакомления, и её невозможно осветить в одном ответе на Stack Overflow. Если вкратце, то для начала вы должны объявить параметр не как Func, Action или Predicate, а как Expression> или Expression>. public string GetSource(Expression> expression) { . . . } В этом случае вы не сможете просто вызывать метод, предварительно его придётся скомпилировать. На зато вы можете разобрать его на составные части. В случае x.Name == "name" в корне дерева будет находиться объект типа BinaryExpression со свойством NodeType равным ExpressionType.Equal. Левый и правый дочерние узлы этого узла будут представлять x.Name и "name" соответственно. Проще всего пройтись в отладчике по этому дереву и посмотреть, какие объекты там встречаются. Чтобы вывести всё это на экран, можно просто вызвать метод expression.ToString(). Но в сложных случаях придётся формировать строку из дерева выражения самостоятельно.

Ответ 2



С помощью рефлексии нельзя получить исходный код метода, так как рефлексия возвращает только ту информацию, которая есть в скомпилированной сборке. Рефлексия может дать IL-байткод метода, но не исходный код, ей неоткуда взять его. Исходный код нужно получать из каких-то других источников: 1) Отладочные символы Если получение исходного кода необходимо только на машине, где ведется разработка, можно воспользоваться отладочными символами. В PDB-файлах, генерируемых компилятором, хранится информация о соответствии строк файлов исходного кода инструкциям в IL-байткоде (именно благодаря этому отладчик может при возникновении исключения может указать на конкретную строку). Определив положение начала и конца метода, считать его код из исходников - дело техники. В символах присутствует каждый метод, который физически есть в сборке, в том числе и анонимные, поэтому решение вполне применимо и для условий из лямбда-выражений. Так как символы ссылаются на исходники по абсолютным путям, чтобы он сработал на других машинах, проект понадобится перенести на них в точности по такому же пути, как он был расположен на машине разработчика. Естественно, необязательно распространять проект целиком - главное, чтобы файл с нужным методом был доступен. Формат PDB не документирован, однако существует несколько библиотек с открытым исходным кодом от Microsoft для работы с ним, например Microsoft.Diagnostics.Runtime. 2) Декомпиляция Если исходники недоступны или нет символов, можно воспользоваться одним из декомпиляторов, чтобы восстановить их из байткода на лету. Например, библиотека ICSharpCode.Decompiler, движок декомпилятора ILSpy, поддерживает декомпиляцию типа в синтаксическое дерево, которое можно обойти и найти нужный метод. В том числе, с помощью нее программа можно спокойно декомпилировать саму себя! Правда, чтобы корректно обрабатывать лямбды, придется отключить подстановку кода анонимных методов - в противном случае мы их в синтаксическом дереве не найдем. Пример получения исходного кода метода двумя данными способами: using System; using System.Text; using System.Collections.Generic; using System.IO; using System.Linq; using System.Diagnostics; using System.Reflection; using Microsoft.Diagnostics.Runtime; using Microsoft.Diagnostics.Runtime.Utilities.Pdb; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; namespace ConsoleTest1 { class Program { public static string GetSourceFromPdb(Predicate match) { int token = match.Method.MetadataToken; //построим путь к файлу символов string module_path = match.Method.Module.FullyQualifiedName; string pdb_path = Path.Combine( Path.GetDirectoryName(module_path), Path.GetFileNameWithoutExtension(module_path) + ".pdb" ); StringBuilder sb = new StringBuilder(); PdbReader reader = new PdbReader(pdb_path); using (reader) { //найдем метод в символах var func = reader.GetFunctionFromToken((uint)token); foreach (PdbSequencePointCollection coll in func.SequencePoints) { //считываем файл исходников string[] lines = File.ReadAllLines(coll.File.Name, System.Text.Encoding.UTF8); //найдем номера строк в файле, соответствующие началу и концу метода var points_sorted = coll.Lines. Where((x)=> x.LineBegin <= lines.Length && x.LineEnd<=lines.Length). OrderBy((x) => x.Offset); PdbSequencePoint start = points_sorted.First(); PdbSequencePoint end = points_sorted.Last(); bool reading = false; int index_start; int index_end; //считаем код метода из исходников for(int i=1; i<=lines.Length;i++) { string line = lines[i-1]; index_start = 0; index_end = line.Length; if (!reading) { if (i >= start.LineBegin) { //первая строка reading = true; index_start = start.ColBegin - 1; if (index_start < 0) index_start = 0; } } if (reading) { if (i >= end.LineEnd) { //последняя строка index_end = end.ColEnd - 1; if (index_end > line.Length) index_end = line.Length; sb.AppendLine(line.Substring(index_start, index_end - index_start)); break; } //считывание текущей строки sb.AppendLine(line.Substring(index_start, index_end - index_start)); } } } } return sb.ToString(); } public static AstNode FindNode(AstNode root,string match) { //поиск метода в синтаксическом дереве var children = root.Children; foreach (AstNode x in children) { var s = x.ToString(); if (s == match && x.Parent.NodeType == NodeType.Member) { return x.Parent; } AstNode res = FindNode(x, match); if (res != null) return res; } return null; } public static string GetSourceDecompiled(Predicate match) { string module_path = match.Method.Module.FullyQualifiedName; var settings = new DecompilerSettings(); settings.AnonymousMethods = false; //отключим подстановку кода анонимных методов var decompiler = new CSharpDecompiler( module_path, settings ); //декомпилируем тип, содержащий метод SyntaxTree tree = decompiler.DecompileType( new ICSharpCode.Decompiler.TypeSystem.FullTypeName(match.Method.DeclaringType.FullName) ); //найдем метод в синтаксическом дереве var children = tree.Children.ToList(); AstNode res = null; foreach (var x in children) { res = FindNode(x, match.Method.Name); if (res != null) break; } string s = ""; if (res != null) s = res.ToString(); return s; } static void Main(string[] args) { string source; Console.WriteLine("*** Source from PDB: ***"); source = GetSourceFromPdb( (s) => s == "Test" || s.Length==0); Console.WriteLine(source); Console.WriteLine("*** Decompiled source: ***"); source = GetSourceDecompiled((s) => s == "Test" || s.Length==0); Console.WriteLine(source); Console.ReadKey(); } } } Результат: *** Source from PDB: *** s == "Test" || s.Length==0 *** Decompiled source: *** [CompilerGenerated] private static bool
b__6 (string s) { return s == "Test" || s.Length == 0; }

пятница, 20 декабря 2019 г.

Ковариантность и контрвариантность в делегатах Action и Func [дубликат]

#c_sharp #action #делегаты


        
             
                
                    
                        
                            This question already has answers here:
                            
                        
                    
                
                        
                            В чем суть ковариантности и контравариантности делегатов?
                                
                                    (2 ответа)
                                
                        
                                Закрыт 2 года назад.
            
                    
Почему параметры T1, T2,... являются контрвариантными (in T1, in T2,...) в Action? 

И почему возвращаемый тип в Func является ковариантным (out TResult)? 
    


Ответы

Ответ 1



На пальцах: если вам требуется парикмахер для собак (Action), подойдёт универсальный парикмахер для животных (функция, принимающая на вход Animal). Поэтому переменной типа Action можно присвоить выражение типа Action. Наоборот, если вам нужно раздобыть какое угодно животное (Func), то магазин по продаже котяток подойдёт (Func). Поэтому переменной типа Func можно присвоить выражение типа Func. Более подробно: В чем суть ковариантности и контравариантности делегатов?, Контравариантность обобщенных делегатов С#.

Ответ 2



Делегаты Action контрвариантны потому что там, где вы можете использовать экземпляр класса-предка, там же вы можете использовать и экземпляр потомка. Пример: Action act1 = str => { int i = str.Length; }; Action act2 = o => { string s = o.ToString(); }; // вы можете сделать такое присваивание act1 = act2; // и затем вызвать делегат вот так, // потому что по факту у вас будет вызван 2-ой делегат, // которому будет передана string, производная от object act1("10"); Так как string - наследник object, то вы можете делать с этим параметром всё то же, что и с object. Ошибки нет. При этом вы не можете сделать так: act2 = act1; Потому что в этом случае вы смогли бы передать некий object в метод, требующий на самом деле string, а это уже ошибка. Схожим образом объясняется и ковариантность Func: Func func1 = () => "str"; Func func2 = () => 10; // такое присваивание тоже возможно func2 = func1; // ваш делегат вернёт некий object, который по факту является строкой. object r = func2(); Аналогично предыдущему примеру, нельзя делать так: func1 = func2; так как в этом случае делегату, возвращающему string, вы можете присвоить делегат, возвращающий object (который в нашем примере фактически имеет тип int).

суббота, 14 декабря 2019 г.

Делегат в качестве параметра

#c_sharp #делегаты


Добрый вечер уважаемые знатоки! У меня возник такой вопрос: мне нужен метод примерно
следующего вида: 
public void MyMethod(Func del)
{
    for(int I = 0; I < 1000; I++)
        del();
}

Собственно в чем проблема: нужно чтобы делегат del вызывался в методе некоторое количество
раз (например тысячу как указано  выше), но дело в том, что Func - это некий "общий"
тип - в действительности это может быть и Func и Func и Func.
Более ого, T1, T2 и T3 могут быть int, float, double. Кроме того я не знаю, как можно
было бы передавать кроме делегата еще и набор его параметров, с которыми он должен
вызываться. Хотелось бы чтобы я имел возможность делать что-то такое: 
var del1 = new Func(() => 10);
var del2 = new Func(i => i);  
var del3 = new Func((i, j) => i + j);

MyMethod(del1);
MyMethod(del2, parametersForDel);
MyMethod(del3, parametersForDel);

Можно ли  как-то написать некий общий метод MyMethod, который мог бы получать в качестве
аргумента любой интересующий меня делегат указанного типа? Или же под каждый Func нужно
писать свой метод? И как быть с передачей параметров для этих делегатов? Заранее спасибо    


Ответы

Ответ 1



О, тестирование производительности! Тестирование — довольно сложная тема, очень сложно сделать его полностью правильно. Считается, что это хорошее задание для новичка, но на самом деле правильные тесты производительности должен бы написать архитектор. Давайте разберём, что мы хотим тестировать. Во-первых, запуск метода один раз — не очень правильная штука. На выполнение метода влияют сотни случайных параметров: например, случайный всплеск активности антивируса или вытесненная на диск страничка кода. Поэтому необходимо прогнать метод много раз: результаты одного прогона статистически иррелевантны. (По поводу того, сколько же раз нужно прогонять тест: не берите число с потолка, а спросите коллегу-математика!) Далее, тест должен быть проведён с одними и теми же параметрами! Иначе наше усреднение не имеет смысла: мы усредняем несравнимые величины. Если в функции одна ветка кода быстрая, а другая медленная, усреднять время пробега по ним бессмысленно: надо иметь два теста, тестирующие разные ветки. Таким образом, мы должны много раз выполнить один и тот же код: вызвать функцию с одними и теми же параметрами. Значит, наш вызов функции прекрасно оборачивается в Action, и для тестирования разных функций можно использовать один и тот же метод: class PerformanceTester { const int repetitions = 1000; // может быть, нужна внешняя параметризация static public TimeSpan ComputeAverageExecutionTime(Action a) { var executionTicks = new List(); for (int i = 0; i < repetitions; i++) executionTicks.Add(MeasureTime(a)); double averageTicks = executionTicks.Average(); return new TimeSpan((long)averageTicks); } static private long MeasureTime(Action a) // in ticks { var stopwatch = new Stopwatch(); stopwatch.Start(); a(); stopwatch.Stop(); return stopwatch.ElapsedTicks; } } Пользоваться так: void f() { /* ... */ } void g(int arg) { /* ... */ } void h(ClassArg arg1, int arg2) { /* ... */ } ... var r1 = PerformanceTester.ComputeAverageExecutionTime(f); var r2 = PerformanceTester.ComputeAverageExecutionTime(() => g(1)); ClassArg arg1 = new ClassArg(); // не включаем конструктор в лямбду var r3 = PerformanceTester.ComputeAverageExecutionTime(() => h(arg1, 0)); Но это — не окончательное решение. В нём есть недостатки, которые мы попробуем закрыть. Во-первых, JIT. Первый пробег функции будет медленнее из-за того, что в этот момент она будет скомпилирована just-in-time-компилятором. Плюс необходимые части кода будут загружены в память. Значит, необходимо включить «разогрев кода» в тест: static public TimeSpan ComputeAverageExecutionTime(Action a) { // разогреваем код: a(); var executionTicks = new List(); for (int i = 0; i < repetitions; i++) executionTicks.Add(MeasureTime(a)); double averageTicks = executionTicks.Average(); return new TimeSpan((long)averageTicks); } Во-вторых, среди большого числа пробегов всё равно будут случайные «выбросы» вверх и вниз. Чтобы нивелировать их, надо знать среднеквадратичное отклонение σ, и отбраковывать результаты, отличающиеся от оценочного среднего на более чем 3σ. Но для этого нужна продвинутая математика, а мы можем по-простому выкинуть наибольшее и наименьшее значения, это тоже довольно хорошая эвристика. var maxVal = executionTicks.Max(); if (executionTicks.Count(v => v == maxVal) < executionTicks.Count / 3) executionTicks.RemoveAll(v => v == maxVal); var minVal = executionTicks.Min(); if (executionTicks.Count(v => v == minVal) < executionTicks.Count / 3) executionTicks.RemoveAll(v => v == minVal); // здесь осталось не менее [[1000 * 2/3] * 2/3] = 444 элементов, // значит, список не пуст, и исключение не выбросится double averageTicks = executionTicks.Average(); В-третьих, давайте-ка посмотрим, что именно мы измеряем. Кроме самой функции, мы измеряем время её косвенного вызова, через лямбду и делегат. Для большинства функций это неважно: время пробега функции обычно значительно больше времени вызова делегата, так что этой тонкостью можно пренебречь. Другое дело, если ваша функция очень маленькая, для этого придётся писать код тестирования вручную. Например, такой: class PerformanceTester { static public TimeSpan ComputeExecutionTimeForSmallFunctions( Action execute1000times, Action execute1time) { // разогреваемся execute1time(); var ticks = MeasureTime(execute1000times); return new TimeSpan((long)(ticks / 1000.0)); } // остальные функции } Используем так: void q() { /* что-то очень быстрое */ } var r4 = PerformanceTester.ComputeExecutionTimeForSmallFunctions( () => { for (int i = 0; i < 1000; i++) q(); } q); Недостатки: в измерение всё же включается косвенный вызов делегата, но теперь делегат вызывается не 1000, а всего 1 раз, и вносит лишь 1/1000 искажения. К счастью, для маленькой функции можно смело поднимать константу 1000 до больших значений, например, до 1000000. Заодно и результаты случайных больших отклонений от среднего, с которыми мы боролись для длинных функций отбрасыванием минимального и максимального значения, нивелируются. Кроме того, ко времени выполнения функции прибавляется время на управление циклом. В-четвёртых, не забывайте, что производительность сильно зависит от настроек компилятора. Поэтому никогда не тестируйте производительность кода, скомпилированного в DEBUG-режиме. Кроме того, пробег в Visual Studio замедляет скорость кода приблизительно вдвое (!), так как отладчик специально «просит» JIT-компилятор оптимизировать поменьше. Поэтому после окончания отладки запускайте тесты производительности только вне Visual Studio, и только скомпилированные в RELEASE-режиме. В-пятых, не забывайте о наличии довольно умного оптимизатора! Он имеет право заинлайнить вызов функции, и если вы игнорируете результат, выкинуть всё вычисление! Поэтому для случая ComputeExecutionTimeForSmallFunctions возвращаемое значение надо бы как-то использовать, хотя бы запоминать в массиве, добавлять в аккумулятор или что-то ещё наподобие.

Ответ 2



А если так? delegate void MyDelegate(params object[] values); public void MyMethod(MyDelegate del) { ... } А вообще такая потребность сомнительна. Может стоит пересмотреть задачу?

пятница, 13 декабря 2019 г.

Что означает эта конструкция с EventHandler?

#c_sharp #события #делегаты #handlers


VS предложил "упростить"

...
EventHandler args = MyHandler
if (args != null)
{
    args(this, e);
}
...


На

...
MyHandler?.Invoke(this, e)
...


Что это значит? Куда копать?
    


Ответы

Ответ 1



В С# 6 появился так называемый null propagation operator. Он позволяет упростить довольно утомительные проверки на null, и означает, что если выражение в его левой части равно null, то он вернёт null, а если оно имеет какое-то другое значение, то будет возвращено значение поля/свойства из правой части, либо будет выполнен метод из правой части. Согласитесь, что писать data = some?.GetData(); явно удобнее, чем if(some != null) data = some.GetData();

Ответ 2



Данная конструкция MyHandler?.Invoke(this, e); логически эквивалентна следующей if ( MyHandler != null ) MyHandler.Invoke(this, e); Такую же конструкцию ввели для оператора индексирования. Например int[] a = null; int? x = a?[0]; a = new int[1] { 10 }; int? b = a?[0]; Это средство появилось в новой версии C# 6.0

Ответ 3



Ключевое слово null является литералом, представляющим пустую ссылку, которая не ссылается ни на один объект. null является значением по умолчанию переменных типа ссылок. Для ключевого слова null определены специальные операторы: int? a; Разрешаем использование null для value type. Вместо int можно указать любой тип значения. obj?.SomeMethod(); Проверяем объект obj на null и, если объект не равен null, обращаемся к члену типа. Также помимо ?. можно обращаться к индексатору ?[]. int y = x ?? -1; Проверяем если x != null, берем его значение, иначе выполняем конструкцию справа от ??, в данном случае устанавливаем y = -1. Также можно комбинировать между собой: int? length = customers?.Length ?? -1; // если customers != null, вернуть customers.Length, иначе вернуть -1

вторник, 10 декабря 2019 г.

Связанный с делегатом объект

#c_sharp #net #делегаты


На msdn прочитал такой абзац 


  A delegate is said to be bound to the method it represents. In
  addition to being bound to the method, a delegate can be bound to an
  object. The object represents the first parameter of the method, and
  is passed to the method every time the delegate is invoked. If the
  method is an instance method, the bound object is passed as the
  implicit this parameter (Me in Visual Basic); if the method is static,
  the object is passed as the first formal parameter of the method, and
  the delegate signature must match the remaining parameters.


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


Ответы

Ответ 1



Прекрасный вопрос! Вы попали на один из «пыльных углов», это редко посещаемая область на карте .NET. Смотрите. У .NET есть два типа делегатов: открытые и замкнутые. Если вы посмотрите на тип Delegate, у него есть свойства Method и Target. Так вот, замкнутые делегаты — это те, которые используют Target, а открытые — те, у которых Target игнорируется.* Как вы помните, вызов нестатического метода можно представить себе мысленно как вызов функции, у которой добавлен ещё один аргумент, представляющий this. (На этой аналогии базируется понятие методов расширения в C#.) Разница между открытыми и замкнутыми делегатами на первый взгляд напоминает разницу между статическими и простыми (нестатическими) методами: вызов простого метода привязан к целевому объекту (то есть, объекту, у которого будет этот самый метод вызван, тому, который будет доступен как this внутри метода). Кажется естественным представлять статические методы открытыми, и нестатические — замкнутыми делегатами. Но эта аналогия обманчива. На самом деле, как статический, так и нестатический метод могут быть «упакованы» как в открытый, так и в замкнутый делегат! В простом случае, когда замкнутый делегат ссылается на простой метод, у Target'а вызывается Method. Что же происходит, когда простой метод попадает в открытый делегат? А вот что. Для вызова такого делегата нужно указать ещё одним аргументом объект, на котором данный метод будет вызван. При этом список аргументов для вызова делегата увеличится на 1. Таким образом, мы как бы отвязываем метод от объекта. Тестовый код. Заводим простой класс и простой метод в нём: class Test { int n; public Test(int n) { this.n = n; } public void Display(int m) { Console.WriteLine($"object {n}, arg {m}"); } } Для создания замкнутых делегатов можно использовать перегрузки Delegate.CreateDelegate с параметром firstArgument, для замкнутых — без этого аргумента**. Ну или подойдут встроенные в язык средства. Тестируем: Test t1 = new Test(1), t2 = new Test(2); // создали два экземпляра Type typeT = typeof(Test); // запомнили тип и MethodInfo для метода MethodInfo miDisplay = typeT.GetMethod("Display"); // замкнутый делегат можно создать так: Action closedInstance1 = (Action)Delegate.CreateDelegate( typeof(Action), t1, miDisplay); // более прямой метод встроен в язык: Action closedInstance2 = t2.Display; // вызываем closedInstance1(10); // object 1, arg 10 closedInstance2(11); // object 2, arg 11 // открытый делегат создаётся так: Action openDelegate1 = (Action)Delegate.CreateDelegate( typeof(Action), miDisplay); // более удобного, встроенного в язык метода создать открытый делегат // из нестатического метода нет openDelegate1(t2, 10); Окей, теперь случай статического метода. В случае открытого делегата вызов прост: статический метод не требует наличия объекта для своего вызова. А что же происходит в случае замкнутого делегата? А вот что. Первый параметр из списка параметров метода фиксируется: его значение берётся всегда из поля Target нашего делегата. Список параметров укорачивается на 1. Таким образом, мы видим, что происходит замыкание по первому аргументу функции. Тестируем: Type typeST = typeof(StaticTest); // запомнили тип и MethodInfo для метода MethodInfo miDisplayEx = typeST.GetMethod("DisplayEx"), miPrintTwo = typeST.GetMethod("PrintTwo"); // открытый делегат можно создать через библиотечную функцию Action openDelegate2 = (Action)Delegate.CreateDelegate( typeof(Action), miDisplayEx); // или прямо через встроенный в язык метод Action openDelegate3 = StaticTest.PrintTwo; // работает и для методов расширения Action openDelegate4 = StaticTest.DisplayEx; // вызываем openDelegate2(t1, 20); // More: object 1, arg 20 openDelegate3("hello", 3); // x = hello, y = 3 openDelegate4(t2, 21); // More: object 2, arg 21 // замкнутый делегат Action closedDelegate2 = (Action)Delegate.CreateDelegate( typeof(Action), "goodbye", miPrintTwo); // для методов расширения есть удобное средство в языке Action closedDelegate3 = t2.DisplayEx; // а вы знали, что так можно? // вызываем closedDelegate2(7); // x = goodbye, y = 7 closedDelegate3(30); // More: object 2, arg 30 Замыкание по первому аргументу значимого типа невозможно***. Суммируя, у нас возможно 4 случая: замкнутый делегат и простой метод: вызывается метод у фиксированного объекта замкнутый делегат и статический метод: первый аргумент метода фиксируется открытый делегат и простой метод: для вызова нужно указать и объект, и аргументы метода открытый делегат и статический метод: вызывается метод как обычно, объект не нужен Имеется ли у этого практическое применение? Наверное, в современном C#, где можно легко «тасовать» сигнатуры функций и проводить любые замыкания (а не только по неявному аргументу) при помощи лямбда-функций, это не так уж и необходимо, и может понадобиться лишь для экстремальной оптимизации, чтобы избежать создания лямбд. Литература: Schabse Laks, Open Delegates vs. Closed Delegates MSDN, документация на класс Delegate, секция Remarks MSDN, документация к методу Delegate.CreateDelegate, секция Remarks *У замкнутого делегата может быть Target == null, как ни странно. При его вызове случится, как и имеет смысл ожидать, NullReferenceException. **Или с аргументом, равным null. Чем же отличается для функции Delegate.CreateDelegate замкнутый по null делегат от открытого, ссылающегося на нестатический метод? Количеством аргументов в сигнатуре делегата, которую вы передаёте первым аргументом! ***Причины этого такие же, как и причины невозможности получения делегата из метода расширения для значимого типа (оно ведёт к ошибке CS1113). О внутренних причинах этого написано здесь. Хотя сообщение об ошибке, которое производит Delegate.CreateDelegate, выглядит совершенно непонятно.

четверг, 28 ноября 2019 г.

Что такое делегат в языке С#?

#c_sharp #c_sharp_faq #делегаты


Объясните простым, человеческим языком, кто такой и зачем нужен делегат в ООП вообще
и в С# в частности?
    


Ответы

Ответ 1



Делегат — это объектно-ориентированный способ работы с методом как с переменной. Его более привычный аналог — указатель на функцию, функтор, или даже просто вектор прерывания (спасибо @rdorn за подсказку). Делегат представляет собой тип, соответствующий определённой сигнатуре функции. Объявив переменную делегатного типа, вы можете записать в неё статический или нестатический метод, передать его как аргумент куда-либо, и вызвать. Классический пример использования делегатов — сортировка списка объектов по значению какого-либо поля. Вы передаёте в сортирующий метод делегат, который по объекту вычисляет ключ сортировки, то есть, вытаскивает значение поля.

Ответ 2



Из справки на MSDN Делегаты (Руководство по программированию на C#): Делегат — это тип, представляющий ссылки на методы с конкретным списком параметров и возвращаемым типом. При создании экземпляра делегата этот экземпляр можно связать с любым методом с совместимой сигнатурой и возвращаемым типом. Метод можно вызвать (активировать) с помощью экземпляра делегата. Добавлю своими словами: делегаты можно назвать особыми типами с кодом, генерируемым на этапе выполнения. Также можно сказать, что это своего рода указатели на методы. Вызов делегатов выполняется в том же потоке через метод, вызванный с использованием делегата, причем исключения бросаются так же, как будто метод был вызван напрямую. Вызов делегата с одним методом равносилен обычному вызову метода. Один делегат может вызвать много методов (если подписаны на несколько методов). Касаемо ООП - как уже было сказано, это объектно-ориентированный способ работы с методом. Вообще, тема ООП достаточно большая, поэтому тут только вскользь. Делегат - это такой же тип, как к примеру класс или интерфейс, он может определяться либо внутри класса, либо вне, в пределах пространства имен. На ruSO C#-делегаты достаточно популярная тема, вопросы и ответы на которую имеются, для изучения приведу пару примеров: Делегаты и их экземпляры Как пользоваться делегатами? Делегаты C#. Invoke c# Делегаты - плюсы и минусы Когда обычно нужно использовать делегаты?

Ответ 3



Уже 2 ответа даны. Я добавлю третий вариант, более человечный :) Делегат - лучше всего вспомнить о таком слове как Представитель. Это слово будет синонимом или, если хотите, переводом на русский яз. Еще можно вспомнить о слове Депутат. Короче, представитель, который представляет в качестве себя одного любимого многих. Депутат представляет выборщиков, много выборщиков. Делегат в языке C# - представляет или может представлять много методов. Этакое отношение один-ко-многим. Делегат похож на переменную, которая может ссылаться на один или больше методов с одинаковой сигнатурой и возвращаемым типом. После создания делегата с нужной сигнатурой, вы можете подписывать (присоединять к списку вызова этого делегата с помощью операции +=) методы или отписывать (удалять из списка вызова операцией -=) методы. В дальнейшем использовав вызов делегата, вы производите вызов всех методов, которые ранее были в списке вызова. Еще одна аналогия - список e-mail рассылки - отправка одного письма сразу многим адресатам. Про использование в ООП: на делегатах основаны события, с помощью делегатов можно создавать анонимные методы (методы без имени), ну и см. ответ уважаемого @VladD.

Ответ 4



Я думаю что остальные излишне сложно написали... Если просто и в двух словах: Делегат - это просто обьект хранящий ссылку(и) на метод(ы). И на примере использования: Обьявляем делегат: delegate int Mydel(int x, int y); Обьявляем метод, который может быть подписан на делегат. Пример кодом: static int Sum(int x, int y) { return x + y; } Этот метод должен соответствовать: Иметь такую же сигнатуру как сам делегат (входные параметры) Возвращать такой же тип данных как и делегат . Мой код, как видно, соответствует этому. Использование делегата: // обьявил переменную делегата и подписал метод Sum Mydel sumDelegate = Sum; //вызвал метод Sum через sumDelegate int result = sumDelegate(10, 5); Что нужно иметь ввиду: Один делегат может вызвать много методов (если подписаны на несколько методов). (подписывать через += и отписывать через -= ) Вызов делегата делается в том же потоке, в котором находится метод, из которого его вызвали. Это значит, что если у вас есть 2 потока в программе, и нужно вызвать метод, который делает что-то из UI потока, нужно вызвать BeginInvoke. Как-то так: Dispatcher.BeginInvoke(sumDelegate(10, 5)); Еще есть предопределенные делегаты Action и Func. Лично я предпочитаю пользоваться именно ими. Еще есть довольно родственное понятие event. Event- ы основаны на делегатах. Про разницу между ними можно почитать вот здесь: Event и delegate: в чем отличие?

понедельник, 25 ноября 2019 г.

В чем суть ковариантности и контравариантности делегатов?


Изучаю по книге работу с делегатами и есть там пример, объясняющий, что такое ковариантность и контравариантность. Решил подробнее поискать в гугле, но объяснений так и не нашел.
В книге сказано, что ковариантность позволяет присвоить делегату метод, возвращаемы
типом которого служит класс, производный от класса, указываемого в возвращаемом типе делегата. А контравариантность позволяет присвоить делегату метод, типом параметра которого служит класс, являющийся базовым для класса, указываемого в объявлении делегата.
К сожалению в практике это так разобрать и не смог, может кто сможет разжевать данный пример подробнее или преподнести свой, более легкий?

class X
{
    public int Val;
}

class Y : X { }

//Этот делегат возвращает объект класса X и принимает объект класса Y в качеств
аргумента
delegate X ChangeIt(Y obj);

class CoContraVariance
{
    //Этот метод возвращает объект класса X и имеет объект класса X в качестве параметра
    static X IncrA(X obj)
    {
        X temp = new X();
        temp.Val = obj.Val + 1;
        return temp;
    }

    //Этот метод возвращает объект класса Y и имеет объект класса Y в качестве параметра
    static Y IncrB(Y obj)
    {
        Y temp = new Y();
        temp.Val = obj.Val + 1;
        return temp;
    }

    static void Main()
    {
        Y Yob = new Y();

        ChangeIt change = IncrA;
        X Xob = change(Yob);

        Console.WriteLine("Xob: {0}", Xob.Val);

        change = IncrB;
        Yob = (Y)change(Yob);

        Console.WriteLine("Yob: {0}", Yob.Val);

        Console.ReadLine();
    }
}

    


Ответы

Ответ 1



Для начала, давайте глянем, что такое эта самая вариантность. Пусть у нас есть два класса, Car и BMW. Очевидно, что BMW есть подкласс Car: каждая бэха является машиной. Обычно при этом говорят так: «везде, где вы используете Car, можно использовать и BMW». Это на самом деле почти правда, но не совсем. Пример: если у вас есть список машин, вы не можете вместо него использовать списо BMW. Почему? А вот почему. Пускай вас есть List, и вы используете его как список машин. Тогда, раз это список машин, в него можно добавить и Запорожец Lanos, правильно? Вот тут-то и начинаются проблемы. Если у вас в коде написано: List bmws = new List(); List cars = bmws; // поскольку список БМВ - это список машин cars.Add(new Lanos()); BMW bmw = bmvs[0]; // ой. Внимательно посмотрите на этот код и подумайте над ним: он иллюстрирует проблему (И он не откомпилируется: язык C# спроектирован так, чтобы не приводить к проблемам.) Проблема с записью в список. Если мы в список добавим произвольную машину, будет очень плохо: мы сможем нарушить гарантии, которые даёт нам система типов! Если бы у нас был список, доступный только на чтение, то проблем бы как раз не было: IEnumerable bmws = new List() { new BMW() }; IEnumerable cars = bmws; // а так можно //cars.Add(new Lanos()); // <-- не скомпилируется Итак, что у нас получается? Несмотря на то, что BMW — машина, список BMW уже не обязательн является списком машин. А вот список BMW, доступный лишь на чтение, таки является списком машин. Есть? Теперь назад к вариантности. Мы говорим о ковариантности в общем смысле, если что-т меняется аналогичным образом. В случае наследования классов: мы можем вместо Car использовать BMW, и точно так же мы можем вместо IEnumerable использовать IEnumerable. Окей, это было длинное вступление, теперь вернёмся к теме: ковариантность делегатов Пусть у нас есть делегат, зависящий от типа Car. Поменяем в его определении Car на BMW, можно ли новый делегат использовать вместо старого? Давайте рассуждать логически. Если у нас есть такой делегат: public delegate Car Replace(Car original); (он принимает на вход Car, и выдаёт другой экземпляр Car), то можно ли вместо него подставить функцию, описывающуюся делегатов такого вида: public BMW MyReplace(BMW original) { ... } ? Разумеется, нет, потому что делегат может принимать на вход любую функцию, а наш функция хочет только BMW. Так что здесь ковариантности нету: такую функцию нельзя использовать там, где требуется данный делегат. А вот если наш вариантный тип данных (то есть, Car) находится лишь в позиции возвращаемого типа: public delegate Car Create(); то на его месте можно использовать функцию такого вида: public BMW CreateBmw() { ... } (если подходила любая машина, то BMW тоже подойдёт). Это и есть ковариантность делегатов: там, где от вас в коде требуется делегат, вы можете вместо него предоставить ковариантный делегат. Пример кода, использующий это: // это функция, принимающая делегат: Car PrepareCar(Create carCreator) { Car car = carCreator(); car.ManufacturingDate = DateTime.Now; car.Mileage = 0; return car; } // это функция, которая ковариантна Create: она возвращает не Car, а BMW BMW BmwFactory() { var bmw = new BMW(); bmw.EnginePower = 400; return bmw; } // вы можете использовать эту функцию как аргумент PrepareCar // хотя её сигнатура другая: return PrepareCar(BmwFactory); Контравариантность работает в другую сторону: там вы можете использовать делегат работающий с базовым типом там, где ожидается делегат с производным типом. Такое работает для аргументов функций: delegate double BmwTester(BMW bmw); void TestAndPublish(BmwTester tester) { var bmw = new BMW(); double testResult = tester(bmw); PublishResult(testResult); } double UniversalTester(Car car) { return 5.0; } // вы можете использовать UniversalTester, хотя у него и не совсем подходящая сигнатура TestAndPublish(UniversalTester); Это работает по тем же причинам, что и ковариантность: если тестеру подходит любой тип машины, то он сможет работать и с BMW тоже.

Ответ 2



Каждый из параметров-типов обобщенного делегата или интерфейса должен быть помече как ковариантный или контравариантный. Это не приводит ни к каким нежелательным последствиям но позволит применять ваших делегатов в большем количестве сценариев и позволит вам осуществлять приведение типа переменной обобщенного делегата к тому же типу делегата с другим параметром-типом. Параметры-типы могут быть: Инвариантными. Параметр-тип не может изменяться. Контравариантными. Параметр-тип может быть преобразован от класса к классу, производному от него. В языке C# контравариантный тип обозначается ключевым словом in. Контравариантный параметр-тип может появляться только во входной позиции, например, в качестве аргументов метода. Ковариантными. Аргумент-тип может быть преобразован от класса к одному из его базовых классов. В языке С# ковариантный тип обозначается ключевым словом out. Ковариантный параметр обобщенного типа может появляться только в выходной позиции, например, в качестве возвращаемого значения метода. Предположим, что существует следующий тип делегата: public delegate TResult MyDelegate(T arg); Здесь параметр-тип T помечен словом in, делающим его контравариантным, а параметр-ти TResult помечен словом out, делающим его ковариантным. Пусть объявлена следующая переменная: MyDelegate fn1 = null; Ее можно привести к типу MyDelegate с другими параметрами-типами: MyDelegate fn2 = fn1; // Явного приведения типа не требуется Exception e = fn2(""); Это говорит о том, что fn1 ссылается на функцию, которая получает Object и возвращае ArgumentException. Переменная fn2 пытается сослаться на метод, который получает Strin и возвращает Exception. Так как мы можем передать String методу, которому требуетс тип Object (тип String является производным от Object), а результат метода, возвращающего ArgumentException, может интерпретироваться как Exception (тип ArgumentException является производным от Exception), представленный здесь программный код откомпилируется, а на этапе компиляции будет сохранена безопасность типов. Примечание Вариантность действует только в том случае, если компилятор сможет установить возможност преобразования ссылок между типами. Другими словами, вариантность неприменима для значимых типов из-за необходимости упаковки (boxing).

воскресенье, 24 ноября 2019 г.

Event и delegate: в чем отличие?


Для того чтобы лучше понять хотел знать чем отличается event от delegate
и ещё применение операторов += и -= для методов в C#
    


Ответы

Ответ 1



Давайте начнём с того, что event и delegate — это две разных вещи, настолько же разные как автомобиль и морковка. Разница между полем-делегатом и event'ом примерно такая же, как между полем и свойством: event иногда выглядит как делегатное поле. Давайте разберёмся в этом. delegate — это класс, содержащий в себе «шаблон» метода, то есть, сигнатуру метода Переменная делегатного типа — объект типа MulticastDelegate (точнее, производного о него), который может содержать один или несколько объектов, представляющих собой метод с совместимой с «шаблоном» сигнатурой (контр- и ковариантность немного усложняет картину). То есть это как бы переменная, которая может содержать функции. Для таких переменных определена операция +, которая комбинирует слагаемые-функции в одну новую функцию, и симметричная операция -. Эти операции автоматически порождают производные операции += и -=. event же — это просто пара методов в классе, обозначаемых как add и remove, и имеющи произвольную семантику, выбранную программистом. (Аналог — геттер и сеттер свойства. В имплементации по умолчанию для event'а заводится скрытое поле делегатного типа, add/remove добавляют или убирают из него методы (под lock'ом). (Чтобы немного запутать картину, это скрытое поле доступно по тому же имени, что и event.) Функции add/remove, составляющие event, вызываются соответственно как += и -=. Никаких операций +/-, разумеется, нету. Давайте ещё пробежимся по отличию event'а в классе от публичного поля делегатного типа. Рассмотрим случай, когда event реализован «по умолчанию», то есть, с неявным делегатным полем. Отличие состоит в том, что: 1) Для делегатного поля у вас полный доступ к нему. Вы можете — также и снаружи класса — разобрать MulticastDelegate на части и собрать новый, вы можете заменить его на сво или присвоить ему null, вы можете его вызвать, можете его скопировать себе в переменную. У вас есть полный доступ, как и к любому публичному полю. (Это, разумеется, вопиющим образом нарушает инкапсуляцию.) Для event'а, вы можете лишь написать instance.Event += handler и instance.Event - handler, что отображается на функции add и remove, которые в свою очередь снова-так вызывают += и -= для автоматически реализованного делегата. Никакого другого доступа у вас нету. Внутри класса вы, однако, можете получить на чтение значение этого делегата, используя имя event'а. (Это нужно, например, для того, чтобы вызвать этот делегат; VB в отличие от C# имеет специальный метод RaiseEvent с нужными проверками.) 2) Ещё одно тонкое отличие (уже упомянутое в комментариях к другим ответам) состои в том, что add/remove вызываются под lock'ом, что делает их потокобезопасными. Однако потокобезопасность event'ов очень сложна (чуть не сказал «невозможна в принципе») (см статью [2]), так что эта разница при правильном программировании несущественна: операции с event'ом обычно должны происходить лишь в одном потоке. Заметьте, что lock не доступен вне add/remove, так что вы не сможете скопировать делегат в локальную переменную под ним для потокобезопасного вызова*: void Raise() { EventHandler copy; lock () copy = MyEvent; if (copy != null) copy(...); } Для случая, когда event реализуется не «по умолчанию», общего нет практически ничего. Методы add и remove могут делать всё, что угодно: int subscriberCount = 0; public event EventHandler MyEvent { add { subscriberCount++; } remove { subscriberCount--; } } Программист может завести делегат самостоятельно и «складывать» туда подписанны обработчики, но это в принципе его добрая воля. С другой стороны, всё же рекомендуется не ломать ожидаемую клиентами семантику класса, и использовать event по назначению. Литература: Jon Skeet: Delegates and Events Eric Lippert: Events and races Кроме того, ключевое слово delegate используется для объявления анонимной функции (более старый параллельный синтаксис для лямбда-выражений). *В современных версиях C# для потокобезопасного вызова рекомендуется паттерн MyEvent?.Invoke(...). Впрочем, с потокобезопасностью у событий не всё гладко, как объяснено в статье [2].

Ответ 2



Главное отличие event от delegate состоит в том, что event может быть запущен только в классе, в котором объявлен. Помимо этого, при наличии event компилятор создает не только соответствующее приватное поле-делегат, но еще и два открытых метода для подписки и ее отмены на события. Из меньших отличий стоит вспомнить о том, что событие, в отличие от делегата, може быть членом интерфейса. Это потому, что делегаты всегда являются полями, тогда как интерфейсы содержать поля не могут. Кроме того, событие, в отличие от делегата, не может быть локальной переменной в методе. А вообще по поводу событий и их взаимосвязи с делегатами почитайте у Рихтера, глава 11, которая так и зовется "События"

среда, 17 июля 2019 г.

Делегирование реализации интерфейсов

Есть некий интерфейс A и есть его наследник интерфейс B. У меня есть объект интерфейса A мне нужно создать класс, реализующий интерфейс B
Вопрос, кроме прямого объявления всех методов из A и вызова соответствующих методов у объекта нет другого способа реализации?
interface A { void SomeMethod1(...); int SomeMethod2(...); ........ }
interface B extends A { ........ }
class MyClass implements B { private A obj; void SomeMethod1(...) { obj.SomeMethod1(...); }
int SomeMethod2(...) { return obj.SomeMethod2(...); } ........ }
Класс, реализующий A неизвестен и недоступен. Так, что отнаследоваться не получится.


Ответ

Вдохновленный советом @ЮрийСПБ, Сделал так:
Объявил интерфейс
interface DelegateA extends A { A getObj();
@Override default void SomeMethod1(...) { getObj().SomeMethod1(...); }
@Override default int SomeMethod2(...) { return getObj().SomeMethod2(...); } ............ } Объявил интерфейс B
interface B extends A { ........ } Сделал реализацию
class MyClass implements B, DelegateA { private A obj;
@Override A getObj() { return obj; } }
Этот способ мне нравится больше, чем предложенный Юрием, т.к. интерфейс B теперь не зависит ни от каких делегатов и может быть реализован автономно

четверг, 16 мая 2019 г.

C# делегаты и анонимные функции

Имеется следующий блок кода :
delegate int StrMod(int a); static void main() { StrMod strmod = delegate{return 10;}; Console.WriteLine(strmod(5)); }
Консоль:
10
Почему на 4 строку компилятор не ругается? Ведь входные параметры анонимной функции не соответствуют параметрам делегата.


Ответ

Вы здесь объявляете делегат с помощью анонимного метода.
StrMod strmod = delegate{return 10;};
Согласно документации об анонимных методах, у анонимного метода допускается опускать список параметров, если они вам не нужны внутри метода(в отличии от лямбд выражений, где список параметров нужно указывать всегда явно). В этом случае, анонимный метод может быть преобразован к делегату с различной сигнатурой.
Anonymous methods enable you to omit the parameter list. This means that an anonymous method can be converted to delegates with a variety of signatures.
Поэтому этот код компилируется успешно.

четверг, 2 мая 2019 г.

Ко/контрвариантность у делегата

Есть такой делегат
delegate void Del(string z);
И ему присваивается метод
public void Foo(object z) {
}
Присваивание происходит нормально, за счет вариантности. Однако если в параметре делегата стринг поменять на int тогда компилятор уже возмущается. Почему ? Полагаю, что это как-то связано с ссылочным/структурным типом. Но ведь все они идут от object


Ответ

Вы правы, вариантность делегатов ограничена ссылочными типами.
Вот нормативная цитата из C# language reference (лежит в \VC#\Specifications\1033\CSharp Language Specification.docx), раздел 15.2 (перевод мой):
15.2 Совместимость делегатов
Метод или делегат M называется совместимым с делегатным типом D, если выполняются следующие условия:
У D и M одинаковое количество параметров, и каждый параметр в D имеет одинаковые модификаторы ref/out, как и соответствующий параметр в M Для каждого параметра, не содержащего ref/out, существует тождественное преобразование (§6.1.1) или неявное ссылочное преобразование (§6.1.6) из типа параметра в D в тип соответствующего параметра в M Для каждого ref-/out-параметра, тип параметра в D в точности тот же, что и в M Существует тождественное или неявное ссылочное преобразование из возвращаемого типа в M к возвращаемому типу в D

Это значит, что типы параметром должны быть либо одинаковы (с точностью до разницы между object и dynamic), либо тип параметра должен быть ссылочным подтипом типа параметра в D. Это исключает значимые типы.

суббота, 27 апреля 2019 г.

Групповые делегаты

Action a1 = () => Console.Write(1); Action a2 = () => Console.Write(2); Action a3 = () => Console.Write(3); ((a1 + a2 + a3) - (a1 + a2))(); ((a1 + a2 + a3) - (a1 + a3))();
Увидел данный пример и стало интересно. Как в данном случае отрабатывает удаление делегата. Что происходит при (a1 + a2) и (a1 + a3), и почему первый вариант удаляет как надо а второй не срабатывает вовсе?


Ответ

Пускай a1 + a2 + a3 превращается в делегат, который условно обозначим [a1, a2, a3], a1 + a2 -- [a1, a2], a1 + a3 -- [a1, a3]
При вычитании в [a1, a2, a3] ищется кусок подряд, который равен вычитаемому. Для [a1, a2] такой кусок найдётся, начиная с индекса 0, а для [a1, a3] -- нет. Поэтому при вычитании [a1, a2, a3] - [a1, a2] получается [a3], а при вычитании [a1, a2, a3] - [a1, a3] подходящий кусок не находится, и вычитание не происходит.

Смысл такого правила в том, что если уж делегат получен сложением одного списка с другими, то это первый список будет найден в суммарном делегате как подсписок. Поэтому вычитание в этом случае имеет смысл.

Цитата из стандарта, параграф 7.8.5:
... Результатом является новый список вызова, состоящий из списка первого операнда с удалёнными элементами списка второго операнда, при условии, что второй список является непрерывным (нетривиальным) подсписком первого [...] В противном случае, результатом является вычитаемое.