Может ли C# неявно преобразовать параметр делегата универсальной функции?

Я пишу метод, который, учитывая входные данные, и функция экстрактора обрабатывают входные данные и возвращают результат. В этом упрощенном примере он просто выдает ошибку, если результат равен нулю:

public static TResult Process<TInput, TResult>(TInput input, Func<TInput, TResult?> processor)
    where TResult : struct
{
    return processor(input) ?? throw new Exception();
}

// for later do note that this overload also exist:
public static TResult Process<TInput, TResult>(TInput input, Func<TInput, TResult?> processor)
    where TResult : class
{
    return processor(input) ?? throw new Exception();
}

Его нельзя назвать так:

bool c = Process(false, x => !x); // Error: The type arguments for method 'Program.Process<TResult>(bool, Func<bool, TResult?>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Идея 1:

Я нашел следующие допустимые способы его вызова:

Func<bool, bool?> processor = x => !x;
bool a = Process(false, processor);

bool b = Process<bool, bool>(false, x => !x);

Но они слишком многословны (общие параметры реального метода, которые обычно довольно сложны). Есть ли способ заставить C# выбрать правильный общий параметр и неявно преобразовать Func<bool, bool> в Func<bool, bool?>?

Идея 2:

Добавление перегрузки для возвращаемых значений, не допускающих значения NULL, невозможно, поскольку у нас уже есть перегрузка для ссылочных типов, с которыми она конфликтует:

public static TResult Process<TInput, TResult>(TInput input, Func<TInput, TResult> processor) // Error: Type 'Program' already defines a member called 'Process' with the same parameter types
    where TResult : struct
{
    return processor(input);
}

Идея 3:

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

bool e = Process(false, x => x ? x : null); // Error: The type arguments for method 'Program.Process<TInput, TResult>(TInput, Func<TInput, TResult?>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Идея 4:

Удаление всех перегрузок, кроме одной, и удаление ее общих ограничений также не работает.


.NET Fiddle: https://dotnetfiddle.net/1TRAwz

Почему бы не создать простую функцию Process, которая принимает структуру/класс и решает, что с ними делать внутри? Это поставщик заблокирован? Назовите это как-нибудь вроде ProcessV2 и живите дальше. Или создайте построитель с гибкими расширениями синтаксиса, например ProcessBuilder<TIn, TOut>, который неявно приводит к TOut.

eocron 21.03.2024 19:17

Вы имеете в виду отсутствие общих ограничений? Это не работает: dotnetfiddle.net/yGZ7iy Или вообще нет дженериков? Это не сработает, потому что TResult становится множеством разных типов, значений, простых объектов, сложных и вложенных объектов... Никакой блокировки поставщика вообще нет, функция процесса - это то, что мы будем часто использовать для развертывания/проверки/преобразования входящего json и Я хочу, чтобы код вызывающего абонента был как можно более простым, поэтому при вызове не было общих параметров.

J2ghz 21.03.2024 19:46

Да, я привел пример. Добавлен ThrowIfNull для случаев, когда TOut является классом? , вы можете сами назначать имена/ограничения по своему усмотрению, но я думаю, вы поняли идею.

eocron 21.03.2024 19:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете свободно говорить, если чувствуете, что вас ограничивают дженерики в C# (которые используют большинство библиотек, таких как LINQ, FluentAssertions и т. д.). Таким образом, вы можете создать свой собственный язык, специфичный для домена:

public class Program
{
    public static void Main()
    {
        bool c = Process.Object(false).Using(x=> !x);
        //or even chain them
        int garbage = Process
            .Object(false)
            .Using(x => !x)
            .Next(x => x ? "a" : null)
            .ThrowIfNull()
            .Next(x => x[0])
            .Do_A_Barrel_Roll()
            .Do_A_Barrel_Roll()
            .Do_A_Barrel_Roll()
            .Next(x => x & 8); //not null as you see

        bool g = Process
            .Object(false)
            .Using(x => x ? x : (bool?)null)
            .ThrowIfNull(); //not null either
    }
}

public class ProcessorInput<TIn>
{
    public Func<TIn> InputProvider { get; set; }
}

public class Processor<TIn, TOut>
{
    public Func<TIn> InputProvider { get; set; }
    public Func<TIn, TOut> OutputProvider { get; set; }
    
    public static implicit operator TOut(Processor<TIn, TOut> processor)
    {
        return processor.Eval();
    }

    public TOut Eval()
    {
        return OutputProvider(InputProvider());
    }
}

public static class Process
{
    public static ProcessorInput<TIn> Object<TIn>(TIn input)
    {
        return Object(() => input);
    }
    public static ProcessorInput<TIn> Object<TIn>(Func<TIn> inputProvider)
    {
        return new ProcessorInput<TIn>() { InputProvider = inputProvider };
    }
    public static Processor<TIn, TOut> Using<TIn, TOut>(this ProcessorInput<TIn> processorInput, Func<TIn, TOut> outputProvider)
    {
        return new Processor<TIn, TOut>()
            { InputProvider = processorInput.InputProvider, OutputProvider = outputProvider };
    }
    public static Processor<TOut, TNextOut> Next<TIn, TOut, TNextOut>(this Processor<TIn, TOut> processor, Func<TOut, TNextOut> nextOutputProvider)
    {
        return new Processor<TOut, TNextOut>
        {
            InputProvider = processor.Eval,
            OutputProvider = nextOutputProvider
        };
    }

    public static Processor<TIn, TOut> Do_A_Barrel_Roll<TIn, TOut>(this Processor<TIn, TOut> processor)
    {
        return new Processor<TIn, TOut>
        {
            InputProvider = processor.InputProvider,
            OutputProvider = (x =>
            {
                Console.WriteLine("Barrel roll!");
                return processor.OutputProvider(x);
            })
        };
    }
    
    public static Processor<TIn, TOut> ThrowIfNull<TIn, TOut>(this Processor<TIn, TOut?> processor)
        where TOut : class
    {
        return new Processor<TIn, TOut>
        {
            InputProvider = processor.InputProvider,
            OutputProvider = (x => processor.OutputProvider(x) ?? throw new ArgumentNullException())
        };
    }
    
    public static Processor<TIn, TOut> ThrowIfNull<TIn, TOut>(this Processor<TIn, TOut?> processor)
        where TOut : struct
    {
        return new Processor<TIn, TOut>
        {
            InputProvider = processor.InputProvider,
            OutputProvider = (x => processor.OutputProvider(x) ?? throw new ArgumentNullException())
        };
    }
}

Ни один из созданных вами дополнительных методов/объектов не помогает решить проблему. Ваш код компилируется только потому, что вы удалили ограничения и изменения, допускающие значение NULL, из-за чего он не работал и не соответствовал показанным требованиям. Вы можете просто удалить ограничения и ссылки на возможность обнуления, чтобы код OP компилировался как единый метод. Но тогда он ведет себя по-другому.

Servy 21.03.2024 19:40

Но… он мог сделать… ThrowIfNull… бегло… или практически все, что угодно с помощью TOut…

eocron 21.03.2024 19:46

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

Servy 21.03.2024 19:53

Ну, я предоставил один.

eocron 21.03.2024 19:55

Как уже упоминалось, вы не предоставили его как для структур, допускающих значение NULL, так и для классов, допускающих значение NULL, и не изменили тип результата, когда это предполагалось. Кроме того, ОП специально требует, чтобы этот метод по-прежнему мог принимать структуры, не допускающие значения NULL, и обеспечивать их неявное преобразование. Это не делает ничего из этого.

Servy 21.03.2024 19:57

Наконец я получаю то, что он хотел. Преобразование в ненулевое значение.

eocron 21.03.2024 20:09
Ответ принят как подходящий

Я думаю, что лучшее решение, которое вы, вероятно, найдете, — это «Идея 3», используя notnull:

public static TResult Process<TInput, TResult>(TInput input, Func<TInput, TResult?> processor)
    where TResult : struct
{
    return processor(input) ?? throw new Exception();
}

public static TResult Process<TInput, TResult>(TInput input, Func<TInput, TResult?> processor)
    where TResult : notnull
{
    return processor(input) ?? throw new Exception();
}

Проблема, которую вы указали в этом подходе, связана с неявной типизацией, которая является более фундаментальной, чем обобщения: C# не может узнать, что null означает нулевое логическое значение в этом выражении.

var a = true ? true : null;

CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'bool' and '<null>'

Поскольку TInput и TResult могут быть разными типами, вам нужно будет дать компилятору подсказку о том, какой тип возвращает ваше выражение:

bool e = Process(false, x => x ? x : (bool?)null);

Надеюсь, это не слишком многословно.

Другие вопросы по теме

Похожие вопросы

Почему общий результат такой, как будто у 12.3 нет смысла?
Активный сеанс выхода из Telegram: Account_GetAuthorizations возвращает 0 хеша для активного сеанса
Пользовательский раскрывающийся список Blazor с выбором HTML и сортировкой по тексту, а не по значению для перечислений
Реализовано управляемое удостоверение для службы приложений для доступа к Application Insights .NET Core 3.1
Переменная «a» имеет ссылочный тип, а «A» — это пользовательский класс. Если значение «a» равно нулю, почему «a is A» дает значение true?
Как мне перебрать групповой блок нескольких типов и при этом прочитать свойство? (Формы Windows С#)
При вызове веб-API ASP.NET Core 6 я получаю сообщение об ошибке 400 неверный запрос
Как преобразовать OpenCvSharp.Mat в Emgu.CV.Mat?
Представить строку как перечисление с помощью NSwag
Добавление страницы бритвы в проект .net Angular с удостоверением Microsoft не наследует авторизацию