Функция, возвращающая именованный ValueTuple и флаг успеха - PullRequest
1 голос
/ 15 января 2020

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

Все варианты, которые я могу придумать, невелики: (

Опция1:

bool MyFunc(T1 param, out (Type1 name1, Type2 name2)? result)

if (!MyFunc(param, out var result)) { .. .. my error handling }

.. do stuff with `result.Value.name1` and `result.Value.name2`

Безобразно с необходимостью определить результат и нуждается в result.Value везде


Опция2:

bool MyFunc(T1 param, out (Type1 name1, Type2 name2)? result)

if (!MyFunc(param, out var result)) { .. .. my error handling }
var (name1, name2) = result.Value;

.. do stuff with name1, name2

Безобразно с необходимостью определить результат и явно уничтожить кортеж

Опция 3 :

(Type1 name1, Type2 name2)? MyFunc(Type3 param) {}

var myFuncRes = myFunc(param);
if (myFuncRes is null) { .. my error handling }

.. do stuff with myFuncRes.Value.name1 etc 

Безобразно с необходимостью myFuncRes и .Value или явное уничтожение кортежа


То, что я хотел бы сделать, это что-то вроде:

(Type1 name1, Type2 name2)? MyFunc(T1 param)

if (var myFuncRes = MyFunc(param) is null) { .. .. my error handling }

.. do stuff with `myFuncRes.name1` and `myFuncRes.name2` directly !

Это мой явное намерение ... но это не разрешено


Существуют ли более эффективные способы или общие решения?

(примечание: я не заинтересован в создании исключений здесь)

Ответы [ 6 ]

4 голосов
/ 15 января 2020

Вы можете сделать:

(Type1 name1, Type2 name2)? MyFunc() {...} 

if (!(MyFunc() is {} myFuncRes))
{
    // Error handling
    return;
}
// use myFuncRes.name1 and myFuncRes.name2

При этом используется сопоставление с образцом . {} - это шаблон свойств, который фактически не задает никаких свойств, и поэтому он соответствует любому объекту, который не является null. Смотрите этот ответ .

Это не очень хорошо читается, хотя, IMO. Существуют дискуссии о добавлении новых шаблонов для улучшения подобных случаев, таких как is not null.

. Также есть предложение , чтобы улучшить случай деструктурирования кортежа в объявлении out, которое будет позвольте написать:

bool MyFunc(out (Type1 name1, Type2 name2) result) {...} 

if (!MyFunc(out var (name1, name2)) 
{

} 
1 голос
/ 16 января 2020

Этот подход может работать, если кортеж не должен использоваться ни для чего, если возвращаемое значение равно false. Когда это происходит, мы просто предоставляем кортеж по умолчанию (который следует незамедлительно игнорировать) вместо того, чтобы пытаться сообщить, что он является нулевым, через обнуляемый тип.

static bool Test(T1 param, out (Type1 name1, Type2 name2) result)
{
    try
    {
        //Successful case
        result = ( X, Y );
        return true;
    }
    catch
    {
        //Failure case
        result = (default(Type1), default(Type1));
        return false;
    }
}

Теперь все, что нужно вызывающей стороне для записи, это:

if (Test(param, out var result))
{
    Console.WriteLine("Your results were {0} and {1}", result.name1, result.name2);
}
1 голос
/ 15 января 2020

Типичный способ вернуть эти результаты:

(bool success, string name1, string name2) MyFunc(bool param)
{
    return param ? (true,"A","B")
                 :default;
}

Значение по умолчанию для bool равно false, поэтому я могу быть ленивым и просто вернуть default

Вы можно использовать сопоставление с шаблоном is для проверки и извлечения значений в одной чистой строке:

if(MyFunc(true) is (true,var name1,var name2)){
 Console.WriteLine("AAA");
}

if(MyFunc(false) is (true,var name3,var name4)){
 Console.WriteLine("Should never enter here");
}

Это будет печатать только "AAA"

Go стиль

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

func f1(arg int) (int, error) {
    if arg == 42 {
        return -1, errors.New("can't work with 42")
    }
    return arg + 3, nil
}

...

if r, e := f1(i); e != nil {
    fmt.Println("f1 failed:", e)
} else {
    fmt.Println("f1 worked:", r)
}

То же самое можно сделать в C#, добавив поле error в кортеж и сопоставив нуль :

(int i,string? error) F1(int arg)
{
    return arg==42? (-1,"Can't work with 42")
                    :(arg+3,null);
}

...


var (i,error) =F1(42);
if (error is string err)
{
    Console.WriteLine($"F1 failed: {err}");
}
else
{
    Console.WriteLine($"F1 worked: {i}");
}

Или

switch (F1(42)) 
{
    case (_,string error):
        Console.WriteLine($"F1 failed: {error}");
        break;
    case (int i,null):
        Console.WriteLine($"F1 worked: {i}");
        break;
}

Или, с выражениями переключателя:

var output = F1(43) switch {
    (_,string error)=>$"F1 failed: {error}",
    (int i,null)    =>$"F1 worked: {i}"
};
Console.WriteLine(output);
1 голос
/ 15 января 2020

C# кортежи предназначены для представления пакета переменных (например, локальных переменных в функции), а не сущностей.

Если вы рассматриваете кортеж как единое целое, вы, скорее всего, делаете что-то не так.

Первоначальный метод, во-первых, должен быть:

bool MyFunc(T1 param, out Type1 name1, out Type2 name2)

И если у вас не может быть параметров (если это например, асинхронный метод): 1010

(bool success, Type1 name1, Type2 name2) MyFunc(T1 param)
1 голос
/ 15 января 2020

Почему бы не использовать вложенные кортежи?

static ((int name1, int name2) result, bool success) Test()
{
    return ((1, 1), true);
}

static void Main()
{
    var x = Test();
    if (x.success)
    {
        Console.WriteLine("Your numbers are {0} and {1}", x.result.name1, x.result.name2);
    }
}
0 голосов
/ 22 января 2020

Я нашел лучший способ, которым я себя чувствую:

    (bool success, (string name1, string name2)) MyFunc() {
      if (DateTime.Now.Year > 2020) return (false, ("", "")); // error 
      return (true, ("some value", "some value"));
    }

    void F() {
      if (!(MyFunc() is (true, var (name1, name2)))) return;
      Console.WriteLine(name1);
      .. do stuff with name1 and name2
    }

Это достаточно ясно и кратко и не вводит ненужных переменных

...