Я пишу метод, который, учитывая входные данные, и функция экстрактора обрабатывают входные данные и возвращают результат. В этом упрощенном примере он просто выдает ошибку, если результат равен нулю:
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.
Я нашел следующие допустимые способы его вызова:
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?>?
Добавление перегрузки для возвращаемых значений, не допускающих значения 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);
}
Преобразуйте перегрузку с общим ограничением 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.
Удаление всех перегрузок, кроме одной, и удаление ее общих ограничений также не работает.
.NET Fiddle: https://dotnetfiddle.net/1TRAwz
Вы имеете в виду отсутствие общих ограничений? Это не работает: dotnetfiddle.net/yGZ7iy Или вообще нет дженериков? Это не сработает, потому что TResult становится множеством разных типов, значений, простых объектов, сложных и вложенных объектов... Никакой блокировки поставщика вообще нет, функция процесса - это то, что мы будем часто использовать для развертывания/проверки/преобразования входящего json и Я хочу, чтобы код вызывающего абонента был как можно более простым, поэтому при вызове не было общих параметров.
Да, я привел пример. Добавлен ThrowIfNull для случаев, когда TOut является классом? , вы можете сами назначать имена/ограничения по своему усмотрению, но я думаю, вы поняли идею.





Вы можете свободно говорить, если чувствуете, что вас ограничивают дженерики в 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 компилировался как единый метод. Но тогда он ведет себя по-другому.
Но… он мог сделать… ThrowIfNull… бегло… или практически все, что угодно с помощью TOut…
Ну, вы не сделали того, о чем просил вопрос. Если вы хотите заставить его это сделать, то, возможно, это могло бы стать ответом на вопрос. На самом деле это никак не решает реальную поставленную проблему, а просто добавляет массу сложностей, которые не добавляют никакой ценности.
Ну, я предоставил один.
Как уже упоминалось, вы не предоставили его как для структур, допускающих значение NULL, так и для классов, допускающих значение NULL, и не изменили тип результата, когда это предполагалось. Кроме того, ОП специально требует, чтобы этот метод по-прежнему мог принимать структуры, не допускающие значения NULL, и обеспечивать их неявное преобразование. Это не делает ничего из этого.
Наконец я получаю то, что он хотел. Преобразование в ненулевое значение.
Я думаю, что лучшее решение, которое вы, вероятно, найдете, — это «Идея 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);
Надеюсь, это не слишком многословно.
Почему бы не создать простую функцию Process, которая принимает структуру/класс и решает, что с ними делать внутри? Это поставщик заблокирован? Назовите это как-нибудь вроде ProcessV2 и живите дальше. Или создайте построитель с гибкими расширениями синтаксиса, например ProcessBuilder<TIn, TOut>, который неявно приводит к TOut.