Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / WinForms, .Net Framework Новый топик    Ответить
 Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
Есть несколько делегатов. Нужно собрать из них один, причем по условиям.

// на входе есть:
Func<IQueryable<MyClass>, IQueryable<MyClass>> func1
Func<IQueryable<MyClass>, IQueryable<MyClass>> func2
Func<IQueryable<MyClass>, IQueryable<MyClass>> func3
Func<IQueryable<MyClass>, IQueryable<MyClass>> func4

// нужно их собрать в один
Func<IQueryable<MyClass>, IQueryable<MyClass>> funcResult;

// причем по условию. как-то так:
funcResult = func1;
if (...)
  funcResult = n => func2(funcResult(n));
if (...)
  funcResult = n => func3(funcResult(n));
if (...)
  funcResult = n => func4(funcResult(n));


Этот код приведет к Stack Overflow, а как по-другому собрать, что-то не могу сообразить, хотя, кажется, лежит на поверхности.
21 авг 18, 16:55    [21649789]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Shocker.Pro,

похоже на бесконечную рекурсию. в твоём же коде ошибки нет.
21 авг 18, 19:01    [21649892]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
Ошибки компиляции нет. Будет рекурсия из-за конвейера, потому что я присваиваю вызов одной и той же переменной.
А мне надо собрать несколько функций друг за другом, но не все, а некоторые по условию
21 авг 18, 19:07    [21649895]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Shocker.Pro,

да, не заметил n =>

зачем этот JavaScript-style?

у тебя уже функция выполняет сборку и выполнение, зачем собирать в делегат?
может хочешь expression tree? )
21 авг 18, 19:17    [21649910]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
Ну получилось так
на входе в функцию есть делегат
в процессе обработки добавляются (или не добавляются) еще несколько
после чего вызывается еще одна функция, в которую этот результирующий делегат передается и которая в конце-концов применяется для запроса к БД.

Впрочем, ты прав, выражение ж нужно )
Впрочем, вопрос-то все равно останется тем же...
21 авг 18, 19:33    [21649922]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Shocker.Pro,

для построения новых функций тебе нужны деревья выражений
ну или emit ))

если хочешь комбинировать, тебе нужны спецификации
шаблон chain-of-responsibility

а ты пытаешься делать как в JavaScript, да и там замыкание функции тоже приведёт к рекурсии

если тебе просто надо исправить этот код, то так:

// на входе есть:
Func<IQueryable<MyClass>, IQueryable<MyClass>> func1
Func<IQueryable<MyClass>, IQueryable<MyClass>> func2
Func<IQueryable<MyClass>, IQueryable<MyClass>> func3
Func<IQueryable<MyClass>, IQueryable<MyClass>> func4

// нужно их собрать в один
Func<IQueryable<MyClass>, IQueryable<MyClass>> funcResult;

// причем по условию. как-то так:
funcResult = func1;
if (...)
{
  var f = funcResult;
  funcResult = n => func2(f(n));
}
if (...)
{
  var f = funcResult;
  funcResult = n => func3(f(n));
}
if (...)
{
  var f = funcResult;
  funcResult = n => func4(f(n));
}


но это не выглядит как нормальное решение )
21 авг 18, 19:48    [21649934]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
Согласен, не выглядит.
Я попытался реализовать что-то типа того:
var query = db.Set<MyClass>();
if (...)
  query = query.Where(n => .....);
if (...)
  query = query.OrderBy(n => .....);
var res = query.ToList();


Только в данном случае функционал оказался разнесен по методам, и вместо конкретного делегата, у меня появился асбстрактный. То есть, конечно, тут вообще должно быть выражение, а не делегат.
Завтра на свежую голову еще раз попробую уже с выражением.
21 авг 18, 20:04    [21649951]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
ViPRos
Member

Откуда:
Сообщений: 9588
Shocker.Pro,

просто разные уровни - метаданные смещены с конечным кодом
надо сгенерировать код и вызвать
21 авг 18, 20:37    [21649964]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Shocker.Pro,

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

Но ты почему-то собираешь делегаты. Это очень странно )))
21 авг 18, 21:32    [21650000]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
hVostt
Выражения ты можешь строить благодаря тому, что IQueryable можно достраивать и прогонять через спецификации.

Но ты почему-то собираешь делегаты. Это очень странно )))
Я может немного переусложнил архитектуру. Но раз вопрос поднят, хочу его добить.
Делегат взялся как раз из того, что мне нужно передавать запрос в "достраиватель" в виде коллбека.
Попробую на примере:
В самом низу стека вызова есть метод, который строит начало запроса, потом выполняет коллбэк для достройки запроса, а потом выполняет его, то есть примерно так:
		private async Task<List<MyClass>> GetMyClasses(Func<IQueryable<MyClass>, IQueryable<MyClass>> extendQueryCallback)
		{
			// формируем первоначальный запрос
			IQueryable<MyClass> query = _db.Set<MyClass>()
				.Include(n => ...)
				.Where(m => ...);
			// достраиваем его, если этого хочет вышестоящий вызов
			if (extendQueryCallback != null)
				query = extendQueryCallback(query);
			// материализуем
			return await workingQuery.ToListAsync().ConfigureAwait(false);
		}


А вот вышестоящий вызов как раз формирует этот "достраиватель" из нескольких условий, плюс у него на входе тоже есть аналогичный достраиватель в виде входного параметра, который тоже надо учесть.

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

В общем, перемудрил на несвежую голову, иногда нужно просто с тобой поболтать, чтобы понять самого себя
Спасибо


ЗЫ:
hVostt
прогонять через спецификации
что ты имел ввиду?
22 авг 18, 04:28    [21650188]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Сон Веры Павловны
Member

Откуда:
Сообщений: 5188
Shocker.Pro
Этот код приведет к Stack Overflow, а как по-другому собрать, что-то не могу сообразить, хотя, кажется, лежит на поверхности.

Ну еще бы. Решарпер такой код сразу подчеркивает с предупреждением Access to modified closure для funcResult внутри func2, func3, func4. Причина очень проста: на каждом шаге комбинирования тот funcResult, который скармливается функции конвейера, и сам funcResult - одно и то же, т.к. выполнение происходит после присваивания. Отсюда и SOF. Стандартный выход - копировать замыкаемую переменную в отдельную локальную. Вырожденный пример, когда комбинируем всё:
var sequence = Enumerable.Range(0, 9).Select(n => n.ToString(CultureInfo.InvariantCulture)).AsQueryable();
Func<IQueryable<string>, IQueryable<string>> func1 = seq => seq.Select(s => $"{s}a").AsQueryable();
Func<IQueryable<string>, IQueryable<string>> func2 = seq => seq.Select(s => $"{s}b").AsQueryable();
Func<IQueryable<string>, IQueryable<string>> func3 = seq => seq.Select(s => $"{s}c").AsQueryable();
Func<IQueryable<string>, IQueryable<string>> func4 = seq => seq.Select(s => $"{s}d").AsQueryable();
var all = func1;
var all1 = all;
all = n => func2(all1(n));
var all2 = all;
all = n => func3(all2(n));
var all3 = all;
all = n => func4(all3(n));
var result = all(sequence);
foreach(var s in result)
  Console.WriteLine(s);

Вывод:

0abcd
1abcd
2abcd
3abcd
4abcd
5abcd
6abcd
7abcd
8abcd
22 авг 18, 06:20    [21650196]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
Не, ну причины-то понятны, я написал этот код как пример.
Надо было просто сделать так:

Func<IQueryable<MyClass>, IQueryable<MyClass>> funcResult = n =>
{
if (...)
  n = func1(n);
if (...)
  n = func2(n);
if (...)
  n = func3(n);
if (...)
  n = func4(n);
22 авг 18, 06:30    [21650197]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
skyANA
Member

Откуда: Зеленоград
Сообщений: 26761
Shocker.Pro
hVostt
прогонять через спецификации
что ты имел ввиду?

Походу шаблон Спецификация и в частности Composite Specification.
22 авг 18, 07:36    [21650210]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
skyANA
Походу шаблон Спецификация и в частности Composite Specification.
А, ну это оверкодинг для моей задачки. Я просто разнес несколько приватных методов, чтобы избежать повторения кода.
22 авг 18, 07:53    [21650214]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Shocker.Pro
skyANA
Походу шаблон Спецификация и в частности Composite Specification.
А, ну это оверкодинг для моей задачки. Я просто разнес несколько приватных методов, чтобы избежать повторения кода.


Ну собственно ты и начал делать эти спецификации, только почему-то не захотел из грамотно оформить )
22 авг 18, 11:40    [21650484]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20686
hVostt
Ну собственно ты и начал делать эти спецификации, только почему-то не захотел из грамотно оформить )
Эти промежуточные методы помимо операций над запросом выполняют и другую логику, для нормальной реализации паттерна, пришлось бы создавать веер классов, а все на самом деле происходит внутри всего лишь одного класса в приватных методах.
Но на будущее посмотрю в сторону использования паттерна в подобных случаях ))
22 авг 18, 11:59    [21650521]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
Siemargl
Member

Откуда: 010100
Сообщений: 6292
Shocker.Pro,

не оно?

https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/delegates/how-to-combine-delegates-multicast-delegates
22 авг 18, 12:41    [21650599]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Shocker.Pro
пришлось бы создавать веер классов


Веер классов может содержать данные, которые применяют или нет спецификацию.

Допустим, самый распространённый случай:

public class IsDeletedSpecification<T> : Specification<T>
{
   private bool _isDeleted;

   public IsDeletedSpecification(bool isDeleted = false) { _isDeleted = isDeleted; }

   public override IQueryable<T> Specify(IQueryable<T> query)
   {
      return query.Where(p => p.IsDeleted == _isDeleted);
   }
   
   ....


    var mySpec = Specification.And(spec1, spec2, spec3);
}


Это идеологический пример. На деле есть готовые решения, которые берёшь и используешь :)

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

И т.д. и т.п., этот на первый взгляд оверхед, очень даже оправдан по всем фронтам.
22 авг 18, 12:44    [21650605]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
Siemargl
Shocker.Pro,

не оно?

https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/delegates/how-to-combine-delegates-multicast-delegates


Нет!!!
22 авг 18, 12:44    [21650608]     Ответить | Цитировать Сообщить модератору
 Re: Собрать конвейер  [new]
hVostt
Member

Откуда:
Сообщений: 16066
hVostt
var mySpec = Specification.And(spec1, spec2, spec3);


это не внутри класса IsDeletedSpecification, это просто отдельный пример )
22 авг 18, 12:45    [21650610]     Ответить | Цитировать Сообщить модератору
Все форумы / WinForms, .Net Framework Ответить