Loading...
vgivanov avatar vgivanov 42 Точки

Задача 05. Applied Arithmetics с Action

Здравейте, някой може ли да обясни защо операциите над референтен тип подаден в Action не променя стойностите му?

Нали при предаване на референтни типове би трябвало да се работи със стойностите към които сочи! 

Пример: https://pastebin.com/a7Q1xSzQ

Тагове:
0
C# Advanced
VasilKotsev avatar VasilKotsev 830 Точки
Best Answer

По принцип параметрите на метод се подават по стойност в C#, това поведение може да се промени с ref keyword-а. В твоя случай заделяш памет за нов List<T> в heap-а и към нея получаваш адрес, който се присвоява в променливата. Когато подадеш тази променлива на делегат/метод тя се подава по стойност (копира се адреса).

Дефакто когато подадеш листа в метод, параметъра и променливата декларирана в началото на Main() метода сочат към един и същи адрес в паметта, но параметъра е локална променлива, която живее само в scope-а на метода/делегата в случая. В момента в който материализираш IEnumerable<T> и извикаш .ToList(), примерно, ти вече създаваш нова памет за нов лист и присвояваш адреса в променливата в метода и тя вече не сочи към оригиналната колекция. Това е правилното поведение. Естествено с ref това може да се промени и да се подаде променливата по референция в сигнатурата на метода, но това няма да сработи с Action<T> делегат, ще ти трябва нормален метод.

Лесно можеш да се увериш ако вместо LINQ extension методите, завъртиш един нормален For цикъл до дължината на колекцията и инкрементираш всяка стойнст.

0
02/06/2019 22:33:02
vgivanov avatar vgivanov 42 Точки

Благодаря за изчерпателния отговор!

Не разбрах какво имаш предвид с това, че чрез ref може да се промени поведението в случая ако предположим, че Action-a е метод? До колко разбрах проблема е, че се заделя нова памет при ToList()

0
VasilKotsev avatar VasilKotsev 830 Точки

Action<T1,..T16> е предефиниран generic делегат, който обвива void метод със или без параметри зависи от това как е деклариран. Общо взето си го представи като pointer към метод. В стария .NET Framework не можеше да се подава ref в Action-и, в .NET Core, до колкото се разрових в нета, е същото.

ref keyword-а ще окаже, че параметъра ще бъде подаден по референция, а не по стойност. Тогава това, което искаш да направиш в твоя код ще сработи, защото всяка операция върху параметъра, в сигнатурата на метода, ще се отрази и на аргумента.

Проблема в дадения код е, че работиш с extension методите на LINQ върху IEnumerable<T>. Когато извикаш .Select() върху колекция се прави проекция на всички елементи в нея, но тя не се материализира докато не се извика: .ToArray(), ToDictionary(), ToList(), FirstOrDefault(), Any() и т.н или не се обходи във Foreach цикъл. В момента в който я материализираш към лист примерно, вече се прави нов обект и съответно се заделя нова памет за него.

В случая твоето решение, или това което искаш да направиш с LINQ, не е най-оптималното. Дори и да си изкараш нормален метод и да подаваш колекцията по референция, ти пак ще връщаш нова колекция с извършените операции при всяка команда, което изобщо не е добре от към performance. Заделянето на нова памет е скъпа операция в C#, връщането всеки път на нова колекция за елементарни аритметични операции не е най-разумното решение, най-малкото защото отдолу седи Garbage Collector-а на CLR-а и ще трябва той да оптимизира паметта вместо теб, което ще се отрази на performance-а в някакъв по-сериозен случай. Най-добре е да си обходиш колекцията с нормален  For цикъл и да си извършваш операциите в него. Така не заделяш нова памет и не се занимаваш с глупости а и ще е по-бързо ако го Benchmark-неш със System.Diagnostics.Stopwatch или някаква 3rd party библиотека.

0
03/06/2019 10:35:49
vgivanov avatar vgivanov 42 Точки

Благодаря!

0
krasizorbov avatar krasizorbov 548 Точки

Здравей,

Мисля че работи, но навсякаде където си писал например:

nums = nums.Select(n => ++n).ToList();

би трябвало да е:

numbers = nums.Select(n => ++n).ToList();

Нали numbers ти е листа с числата?

Пробвах така и работи.

0
vgivanov avatar vgivanov 42 Точки

Благодаря!

Така работи да, но идеята при делегатите е да не познават обкръжаващата ги среда.

0
krasizorbov avatar krasizorbov 548 Точки

Здравей,

Ами тогава си направи например 4 Func:

Func<List<int>, List<int>> add = x => x.Select(y => y + 1).ToList();
Func<List<int>, List<int>> multiply = x => x.Select(y => y * 2).ToList();
Func<List<int>, List<int>> subtract = x => x.Select(y => y - 1).ToList();
Func<List<int>, string> print = x => String.Join(" ", x);

Така ще имаш лист на входа  и ще подаваш съшщия лист на изхода.:)

0
vgivanov avatar vgivanov 42 Точки

Да в крайна сметка и аз нещо подобно направих.

Благодаря!

0
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.