Возврат нескольких значений вызывающему методу

Ответы:

610

В C # 7 и выше, смотрите этот ответ .

В предыдущих версиях вы можете использовать .NET 4.0 + Tuple :

Например:

public Tuple<int, int> GetMultipleValue()
{
     return Tuple.Create(1,2);
}

Кортежи с двумя значениями имеют Item1и Item2свойства.

Hadas
источник
8
Было бы очень хорошо, если бы вместо Item1, Item2 и т. Д. Можно было использовать именованные выходные значения. C # 7, возможно, собирается обеспечить это .
Снаđошƒаӽ
1
@ Sнаđошƒаӽ абсолютно прав: ожидается, что эта поддержка будет поддерживаться в следующем C # 7.0 с использованием следующего синтаксиса: public (int sum, int count) GetMultipleValues() { return (1, 2); }Этот пример взят из нашего примера с документацией по этому вопросу .
Джеппе Стиг Нильсен
438

Теперь, когда C # 7 выпущен, вы можете использовать новый включенный синтаксис Tuples

(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}

который затем можно использовать так:

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

Вы также можете предоставить имена своим элементам (чтобы они не были "Item1", "Item2" и т. Д.). Вы можете сделать это, добавив имя к подписи или методы возврата:

(string first, string middle, string last) LookupName(long id) // tuple elements have names

или

return (first: first, middle: middle, last: last); // named tuple elements in a literal

Их также можно деконструировать, что является довольно приятной новой функцией:

(string first, string middle, string last) = LookupName(id1); // deconstructing declaration

Проверьте эту ссылку, чтобы увидеть больше примеров того, что можно сделать :)

Франсиско Норьега
источник
11
Если вы ориентируетесь на что-то более раннее, чем .NET Framework 4.7 или .NET Core 2.0, вам необходимо установить пакет NuGet .
Фил
1
Чтобы получить возврат, вы можете сделать: «var result = LookupName (5); Console.WriteLine (result.middle)».
alansiqueira27
204

Вы можете использовать три разных способа

1. параметры ref / out

используя ref:

static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    int add = 0;
    int multiply = 0;
    Add_Multiply(a, b, ref add, ref multiply);
    Console.WriteLine(add);
    Console.WriteLine(multiply);
}

private static void Add_Multiply(int a, int b, ref int add, ref int multiply)
{
    add = a + b;
    multiply = a * b;
}

используя:

static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    int add;
    int multiply;
    Add_Multiply(a, b, out add, out multiply);
    Console.WriteLine(add);
    Console.WriteLine(multiply);
}

private static void Add_Multiply(int a, int b, out int add, out int multiply)
{
    add = a + b;
    multiply = a * b;
}

2. структура / класс

используя структуру:

struct Result
{
    public int add;
    public int multiply;
}
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    var result = Add_Multiply(a, b);
    Console.WriteLine(result.add);
    Console.WriteLine(result.multiply);
}

private static Result Add_Multiply(int a, int b)
{
    var result = new Result
    {
        add = a * b,
        multiply = a + b
    };
    return result;
}

используя класс:

class Result
{
    public int add;
    public int multiply;
}
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    var result = Add_Multiply(a, b);
    Console.WriteLine(result.add);
    Console.WriteLine(result.multiply);
}

private static Result Add_Multiply(int a, int b)
{
    var result = new Result
    {
        add = a * b,
        multiply = a + b
    };
    return result;
}

3. Кортеж

Класс кортежа

static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    var result = Add_Multiply(a, b);
    Console.WriteLine(result.Item1);
    Console.WriteLine(result.Item2);
}

private static Tuple<int, int> Add_Multiply(int a, int b)
{
    var tuple = new Tuple<int, int>(a + b, a * b);
    return tuple;
}

C # 7 кортежей

static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    (int a_plus_b, int a_mult_b) = Add_Multiply(a, b);
    Console.WriteLine(a_plus_b);
    Console.WriteLine(a_mult_b);
}

private static (int a_plus_b, int a_mult_b) Add_Multiply(int a, int b)
{
    return(a + b, a * b);
}
Деврим Алтынкурт
источник
3
Только из-за моей смелости, что бы вы назвали самой быстрой и «лучшей практикой»?
Нетферрет
лучший пример для «использования struct» :)
SHEKHAR SHETE
1
предлагая добавить синтаксис c # 7 (и все больше людей голосуют за это :))
twomm
Для вашей информации, небольшая (не относящаяся к делу) опечатка: в решениях struct / class вы перепутали сложение / умножение.
Szak1
Я не знал, что синтаксис C # Tuple - вещь! Узнайте что-то новое даже спустя годы!
Джаагсма
75

Вы не можете сделать это в C #. То, что вы можете сделать, это иметь outпараметр или вернуть свой собственный класс (или структуру, если вы хотите, чтобы он был неизменным).

Использование параметра out
public int GetDay(DateTime date, out string name)
{
  // ...
}
Использование пользовательского класса (или структуры)
public DayOfWeek GetDay(DateTime date)
{
  // ...
}

public class DayOfWeek
{
  public int Day { get; set; }
  public string Name { get; set; }
}
Самуил
источник
24
Альтернативой в этом случае является использование структуры вместо класса для возвращаемого типа. Если возвращаемое значение не имеет состояния и является временным, лучше выбрать struct.
Майкл Медоус
1
Это невозможно с помощью asyncметодов. Tupleэто путь (Я использую outпараметры в синхронных операциях, хотя они действительно полезны в этих случаях.)
Codefun64
5
Теперь это возможно в C # 7: (int, int) Method () {return (1, 2); }
Spook
4
Ответ должен быть обновлен, он стал совершенно неправильным в последних версиях c #. изменит downvote на upvote, если обновится.
Whitneyland
Работа над унаследованной кодовой базой, возвращение собственного класса было для меня солидным подходом.
Брант
38

Если вы имеете в виду возврат нескольких значений, вы можете либо вернуть класс / структуру, содержащую значения, которые вы хотите вернуть, либо использовать ключевое слово «out» для ваших параметров, например, так:

public void Foo(int input, out int output1, out string output2, out string errors) {
    // set out parameters inside function
}
Крис Доггетт
источник
2
Я не думаю, что хорошо использовать «out» или «ref» - ​​потому что он может быть полностью заменен возвращаемым значением вашего собственного типа класса. Вы видите, если с помощью «ref», как назначить такие параметры? (Это зависит только от того, как кодировать внутри). Если в теле функции автор «обновил» экземпляр параметра с помощью «ref», это означает, что вы можете просто передать «обнуляемое» значение. В противном случае нет. Так что это немного неоднозначно. И у нас есть лучшие способы (1. Возвращение вашего собственного класса, 2. Turple).
33

Предыдущий постер прав. Вы не можете вернуть несколько значений из метода C #. Тем не менее, у вас есть несколько вариантов:

  • Вернуть структуру, которая содержит несколько членов
  • Вернуть экземпляр класса
  • Используйте выходные параметры (используя ключевые слова out или ref )
  • Используйте словарь или пару ключ-значение в качестве вывода

За и против здесь часто трудно понять. Если вы возвращаете структуру, убедитесь, что она мала, потому что структуры являются типом значения и передаются в стек. Если вы возвращаете экземпляр класса, здесь есть некоторые шаблоны проектирования, которые вы, возможно, захотите использовать, чтобы избежать проблем - члены классов могут быть изменены, потому что C # передает объекты по ссылке (у вас нет ByVal, как в VB ).

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

С архитектурной точки зрения я бы рекомендовал не использовать пары ключ-значение или словари. Я считаю, что этот стиль кодирования требует «секретных знаний» в коде, который использует метод. Он должен заранее знать, какими будут ключи и что означают значения, и если разработчик, работающий над внутренней реализацией, изменит способ создания словаря или KVP, он может легко создать каскад сбоев во всем приложении.

Кевин Хоффман
источник
И вы также можете бросить, Exceptionесли второе значение, которое вы хотите вернуть, является дизъюнктивным по отношению к первому: например, когда вы хотите вернуть либо тип успешного значения, либо тип неудачного значения.
Cœur
21

Вы либо возвращает экземпляр класса или использовать из параметров. Вот пример выходных параметров:

void mymethod(out int param1, out int param2)
{
    param1 = 10;
    param2 = 20;
}

Назовите это так:

int i, j;
mymethod(out i, out j);
// i will be 20 and j will be 10
Keltex
источник
3
Помните, хотя это только потому, что вы можете, не означает, что вы должны это делать. В большинстве случаев это считается плохой практикой в ​​.Net.
Майкл Медоус
4
Можете ли вы объяснить, почему это плохая практика?
Зо имеет
Это плохая практика в C / C ++. Проблема заключается в «программировании с помощью побочного эффекта»: int GetLength (char * s) {int n = 0; while (s [n]! = '\ 0') n ++; s [1] = 'X'; возврат (n); } int main () {char приветствие [5] = {'H', 'e', ​​'l', 'p', '\ 0'}; int len ​​= GetLength (приветствие); cout << len << ":" << приветствие; // Вывод: 5: HXlp} В C # вы должны написать: int len ​​= GetLength (ref приветствие), которое будет сигнализировать большой предупреждающий флаг «Эй, приветствие не будет таким же после того, как вы вызовете это», и очень уменьшить количество ошибок
Dustin_00
19

Есть много способов; но если вы не хотите создавать новый объект или структуру или что-то подобное, вы можете сделать, как показано ниже, после C # 7.0 :

 (string firstName, string lastName) GetName(string myParameter)
    {
        var firstName = myParameter;
        var lastName = myParameter + " something";
        return (firstName, lastName);
    }

    void DoSomethingWithNames()
    {
        var (firstName, lastName) = GetName("myname");

    }
nzrytmn
источник
13

В C # 7 появился новый Tupleсинтаксис:

static (string foo, int bar) GetTuple()
{
    return ("hello", 5);
}

Вы можете вернуть это как запись:

var result = GetTuple();
var foo = result.foo
// foo == "hello"

Вы также можете использовать новый синтаксис деконструктора:

(string foo) = GetTuple();
// foo == "hello"

Будьте осторожны с сериализацией, однако, все это синтаксический сахар - в фактическом скомпилированном коде это будет Tuple<string, int>( согласно принятому ответу ) с Item1и Item2вместо fooи bar. Это означает, что сериализация (или десериализация) будет использовать вместо этих имен свойств.

Итак, для сериализации объявите класс записи и верните его вместо этого.

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

if(int.TryParse("123", out int result)) {
    // Do something with result
}

Однако в основном вы будете использовать это в собственных библиотеках .NET, а не в своих собственных функциях.

Кит
источник
Обратите внимание, что в зависимости от выбранной версии .Net может потребоваться установка пакета Nuget System.ValueTuple.
Licht
я собирался ответить, как указано выше ;-)
Джеяра
12

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

Другие ответы приведены с использованием Tuple, который я бы тоже рекомендовал, но с использованием новой функции, представленной в C # 7.0.

(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

Дополнительную информацию можно найти здесь .

Луис Тейджон
источник
11

Есть несколько способов сделать это. Вы можете использовать refпараметры:

int Foo(ref Bar bar) { }

Это передает ссылку на функцию, тем самым позволяя функции изменять объект в стеке вызывающего кода. Хотя это технически не «возвращаемое» значение, это способ заставить функцию сделать нечто подобное. В приведенном выше коде функция будет возвращать intи (потенциально) изменять bar.

Другой аналогичный подход заключается в использовании outпараметра. outПараметр идентичен refпараметру с дополнительным, компилятор применяются правила. Это правило заключается в том, что если вы передаете outпараметр в функцию, эта функция должна установить его значение перед возвратом. Помимо этого правила, outпараметр работает так же, как refпараметр.

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

class FooBar 
{
    public int i { get; set; }
    public Bar b { get; set; }
}

FooBar Foo(Bar bar) { }

Этот последний подход проще и легче для чтения и понимания.

Эндрю Хэйр
источник
11

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

Тем не менее, есть несколько альтернатив:

Вы можете вернуть массив объектов типа с несколькими значениями, которые вы хотите в нем.

private object[] DoSomething()
{
    return new [] { 'value1', 'value2', 3 };
}

Вы можете использовать outпараметры.

private string DoSomething(out string outparam1, out int outparam2)
{
    outparam1 = 'value2';
    outparam2 = 3;
    return 'value1';
}
dustyburwell
источник
10

В C # 4 вы сможете использовать встроенную поддержку для кортежей, чтобы справиться с этим легко.

А пока есть два варианта.

Во-первых, вы можете использовать параметры ref или out для назначения значений вашим параметрам, которые возвращаются в вызывающую подпрограмму.

Это выглядит так:

void myFunction(ref int setMe, out int youMustSetMe);

Во-вторых, вы можете заключить возвращаемые значения в структуру или класс и передать их обратно как члены этой структуры. KeyValuePair хорошо работает для 2 - для более чем 2 вам понадобится пользовательский класс или структура.

Рид Копси
источник
7

Вы можете попробовать эту "KeyValuePair"

private KeyValuePair<int, int> GetNumbers()
{
  return new KeyValuePair<int, int>(1, 2);
}


var numbers = GetNumbers();

Console.WriteLine("Output : {0}, {1}",numbers.Key, numbers.Value);

Вывод :

Выход: 1, 2

Рикин Патель
источник
5

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

Хосе Базилио
источник
4

В основном есть два метода. 1. Использовать параметры out / ref. 2. Возвращать массив объектов.

blitzkriegz
источник
Также есть кортежи и множественные возвращаемые значения как синтаксический сахар для кортежей.
Ноябрь
4

Вот основные Twoметоды:

1) Использование ' out' в качестве параметра. Вы можете использовать 'out' как для 4.0, так и для минорных версий.

Пример 'out':

using System;

namespace out_parameter
{
  class Program
   {
     //Accept two input parameter and returns two out value
     public static void rect(int len, int width, out int area, out int perimeter)
      {
        area = len * width;
        perimeter = 2 * (len + width);
      }
     static void Main(string[] args)
      {
        int area, perimeter;
        // passing two parameter and getting two returning value
        Program.rect(5, 4, out area, out perimeter);
        Console.WriteLine("Area of Rectangle is {0}\t",area);
        Console.WriteLine("Perimeter of Rectangle is {0}\t", perimeter);
        Console.ReadLine();
      }
   }
}

Вывод:

Площадь прямоугольника 20

Периметр прямоугольника 18

* Примечание: * out-keyword описывает параметры, чьи фактические местоположения переменных копируются в стек вызываемого метода, где эти же местоположения могут быть переписаны. Это означает, что вызывающий метод получит доступ к измененному параметру.

2) Tuple<T>

Пример кортежа:

Возврат нескольких значений DataType с использованием Tuple<T>

using System;

class Program
{
    static void Main()
    {
    // Create four-item tuple; use var implicit type.
    var tuple = new Tuple<string, string[], int, int[]>("perl",
        new string[] { "java", "c#" },
        1,
        new int[] { 2, 3 });
    // Pass tuple as argument.
    M(tuple);
    }

    static void M(Tuple<string, string[], int, int[]> tuple)
    {
    // Evaluate the tuple's items.
    Console.WriteLine(tuple.Item1);
    foreach (string value in tuple.Item2)
    {
        Console.WriteLine(value);
    }
    Console.WriteLine(tuple.Item3);
    foreach (int value in tuple.Item4)
    {
        Console.WriteLine(value);
    }
    }
}

Вывод

perl
java
c#
1
2
3

ПРИМЕЧАНИЕ. Использование Tuple действительно с версии 4.0 и выше . TupleТип это class. Он будет размещен в отдельном месте в управляемой куче в памяти. После того, как вы создадите Tuple, вы не сможете изменить его значения fields. Это делает Tupleбольше похожим на struct.

SHEKHAR SHETE
источник
4
<--Return more statements like this you can --> 

public (int,string,etc) Sample( int a, int b)  
{
    //your code;
    return (a,b);  
}

Вы можете получить код как

(c,d,etc) = Sample( 1,2);

Надеюсь это работает.

cruzier
источник
3

Метод, принимающий делегат, может предоставить вызывающей стороне несколько значений. Это заимствовано из моего ответа здесь и использует немного из принятого ответа Хадаса .

delegate void ValuesDelegate(int upVotes, int comments);
void GetMultipleValues(ValuesDelegate callback)
{
    callback(1, 2);
}

Вызывающие операторы предоставляют лямбда (или именованную функцию), а подсказки intellisense копируют имена переменных из делегата.

GetMultipleValues((upVotes, comments) =>
{
     Console.WriteLine($"This post has {upVotes} Up Votes and {comments} Comments.");
});
Скотт Тернер
источник
2

Просто используйте в ООП класс, как это:

class div
{
    public int remainder;

    public int quotient(int dividend, int divisor)
    {
        remainder = ...;
        return ...;
    }
}

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

Таким образом, вы можете иметь много дополнительных «возвращаемых значений», что очень полезно, если вы реализуете вызовы базы данных или сети, где может потребоваться множество сообщений об ошибках, но только в случае возникновения ошибки.

Я ввел это решение также в вопрос C ++, на который ссылается OP.

Roland
источник
2

Из этой статьи вы можете использовать три варианта, как сказано выше.

KeyValuePair - самый быстрый способ.

выход на втором.

Кортеж самый медленный.

Во всяком случае, это зависит от того, что лучше для вашего сценария.

maoyang
источник
2

Будущая версия C # будет включать именованные кортежи. Посмотрите этот сеанс на канале 9 для демонстрации https://channel9.msdn.com/Events/Build/2016/B889

Перейдите к 13:00 для кортежей. Это позволит такие вещи, как:

(int sum, int count) Tally(IEnumerable<int> list)
{
// calculate stuff here
return (0,0)
}

int resultsum = Tally(numbers).sum

(неполный пример из видео)

Нильс
источник
2

Вы можете использовать динамический объект. Я думаю, что он лучше читается, чем Tuple.

static void Main(string[] args){
    var obj = GetMultipleValues();
    Console.WriteLine(obj.Id);
    Console.WriteLine(obj.Name);
}

private static dynamic GetMultipleValues() {
    dynamic temp = new System.Dynamic.ExpandoObject();
    temp.Id = 123;
    temp.Name = "Lorem Ipsum";
    return temp;
}
Ogge
источник
3
Вы теряете проверку типов времени компиляции.
Миха Виденманн
1

Способы сделать это:

1) KeyValuePair (лучшая производительность - 0,32 нс):

    KeyValuePair<int, int> Location(int p_1, int p_2, int p_3, int p_4)
    {                 
         return new KeyValuePair<int,int>(p_2 - p_1, p_4-p_3);
    }

2) кортеж - 5,40 нс:

    Tuple<int, int> Location(int p_1, int p_2, int p_3, int p_4)
    {
          return new Tuple<int, int>(p_2 - p_1, p_4-p_3);
    }

3) out (1.64 нс) или ref 4) Создайте свой собственный класс / структуру

нс -> наносекунды

Ссылка: множественные возвращаемые значения .

Адхам Сабри
источник
0

ты можешь попробовать это

public IEnumerable<string> Get()
 {
     return new string[] { "value1", "value2" };
 }
БЕССМЕРТНЫЙ
источник
1
Это на самом деле не возвращает несколько значений . Возвращает одно значение коллекции.
Мэтью Хауген
Кроме того, почему бы не использовать, yield return "value1"; yield return "value2";чтобы не пришлось явно создавать новый string[]?
Томас Флинков
0

Вы также можете использовать OperationResult

public OperationResult DoesSomething(int number1, int number2)
{
// Your Code
var returnValue1 = "return Value 1";
var returnValue2 = "return Value 2";

var operationResult = new OperationResult(returnValue1, returnValue2);
return operationResult;
}
Жуань
источник
-7

Быстрый ответ специально для возвращаемых типов массивов:

private int[] SumAndSub(int A, int B)
{
    return new[] { A + B, A - B };
}

С помощью:

var results = SumAndSub(20, 5);
int sum = results[0];
int sub = results[1];
aliesTech
источник
3
Что вы подразумеваете под «программистам нужно время и непростительные методы»?
Томас Флинков
2
Вы использовали результаты [0] дважды. это признак того, что не так с этим
symbiont
1
Нет сомнений в том, что это незабываемый ответ
Луис Тейджон