Zwróć wiele wartości do metody wywołującej

439

Przeczytałem wersję tego pytania w C ++, ale tak naprawdę jej nie rozumiałem.

Czy ktoś może jasno wyjaśnić, czy można to zrobić i jak?

Popiół
źródło

Odpowiedzi:

609

W C # 7 i powyżej zobacz tę odpowiedź .

W poprzednich wersjach można używać Tuple .NET 4.0 + :

Na przykład:

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

Krotki z dwiema wartościami mają Item1i Item2jako właściwości.

Hadas
źródło
8
Byłoby bardzo miło, gdyby zamiast Item1, Item2 itd. Można było użyć nazwanych wartości wyjściowych. C # 7 prawdopodobnie to zapewni .
Sнаđошƒаӽ
1
@ Sнаđошƒаӽ ma absolutną rację, oczekuje się, że będzie on obsługiwany w nadchodzącym C # 7.0 przy użyciu składni takiej jak: public (int sum, int count) GetMultipleValues() { return (1, 2); }Ten przykład został zaczerpnięty z naszego przykładu tematu Dokumentacja na ten temat .
Jeppe Stig Nielsen
435

Teraz, gdy C # 7 zostało wydane, możesz używać nowej dołączonej składni Tuples

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

które można następnie wykorzystać w następujący sposób:

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

Możesz także nadać nazwy swoim elementom (więc nie są to „Item1”, „Item2” itp.). Możesz to zrobić, dodając nazwę do podpisu lub metody zwrotu:

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

lub

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

Można je również zdekonstruować, co jest całkiem fajną nową funkcją:

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

Sprawdź ten link, aby zobaczyć więcej przykładów tego, co można zrobić :)

Francisco Noriega
źródło
11
Jeśli celujesz w coś wcześniejszego niż .NET Framework 4.7 lub .NET Core 2.0, musisz zainstalować pakiet NuGet .
Phil
1
Aby uzyskać zwrot, możesz: „var result = LookupName (5); Console.WriteLine (result.middle)”.
alansiqueira27
204

Możesz użyć trzech różnych sposobów

1. parametry ref / out

przy użyciu 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;
}

używając:

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 / class

przy użyciu struct:

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;
}

za pomocą klasy:

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. Tuple

Klasa Tuple

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 krotek

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);
}
Devrim Altınkurt
źródło
3
Tylko dla mojej własnej zarozumiałości, która według ciebie jest najszybsza i „najlepsza praktyka”?
Netferret
najlepszy przykład dla „using struct” :)
SHEKHAR SHETE
1
sugerując dodanie składni c # 7 (i więcej osób głosuje na to :))
twomm
Dla twojej informacji, mała (nieistotna) literówka: W rozwiązaniach struct / class pomieszałeś dodawanie / mnożenie.
Szak1
Nie wiedziałem, że składnia C # Tuple jest rzeczą! Naucz się czegoś nowego nawet po latach!
jhaagsma
75

Nie możesz tego zrobić w C #. Możesz mieć outparametr lub zwrócić własną klasę (lub struct, jeśli chcesz, aby była niezmienna).

Korzystanie z parametru
public int GetDay(DateTime date, out string name)
{
  // ...
}
Używanie niestandardowej klasy (lub struktury)
public DayOfWeek GetDay(DateTime date)
{
  // ...
}

public class DayOfWeek
{
  public int Day { get; set; }
  public string Name { get; set; }
}
Samuel
źródło
24
Alternatywą w tym przypadku jest użycie struktury zamiast klasy dla typu zwracanego. Jeśli zwracana wartość jest bezstanowa i przejściowa, struct jest lepszym wyborem.
Michael Meadows
1
Nie jest to możliwe w przypadku asyncmetod. Tuplejest droga. (Używam jednak outparametrów w operacjach synchronicznych; w takich przypadkach są one rzeczywiście przydatne.)
Codefun64
5
Jest to teraz możliwe w C # 7: (int, int) Method () {return (1, 2); }
Spook
4
Odpowiedź musi zostać zaktualizowana, stało się zupełnie nie tak z najnowszymi wersjami c #. zmieni aktualizację w górę w górę w przypadku aktualizacji.
whitneyland
Praca nad starszą bazą kodu, zwracanie niestandardowej klasy było dla mnie solidnym podejściem.
Brant
38

Jeśli masz na myśli zwracanie wielu wartości, możesz albo zwrócić klasę / strukturę zawierającą wartości, które chcesz zwrócić, albo użyć słowa kluczowego „out” w parametrach, na przykład:

public void Foo(int input, out int output1, out string output2, out string errors) {
    // set out parameters inside function
}
Chris Doggett
źródło
2
Nie sądzę, że warto używać „out” lub „ref” - ponieważ można go całkowicie zastąpić wartością zwracaną przez własny typ klasy. widzisz, jeśli używasz „ref”, jak przypisać takie parametry? (To zależy tylko od tego, jak zakodować w środku). Jeśli w treści funkcji autor „zaktualizował” instancję do parametru za pomocą „ref”, oznacza to, że możesz po prostu przekazać tam wartość „zerowalną”. Inne nie. To trochę niejednoznaczne. I mamy lepsze sposoby (1. Zwracanie posiadanej klasy, 2. Turple).
33

Poprzedni plakat ma rację. Nie można zwrócić wielu wartości z metody C #. Masz jednak kilka opcji:

  • Zwraca strukturę zawierającą wiele elementów
  • Zwraca instancję klasy
  • Użyj parametrów wyjściowych (używając słów kluczowych out lub ref )
  • Jako wyniku należy użyć słownika lub pary klucz-wartość

Tutaj zalety i wady są często trudne do zrozumienia. Jeśli zwracasz strukturę, upewnij się, że jest mała, ponieważ struktury są typu wartości i są przekazywane na stos. Jeśli zwracasz instancję klasy, możesz tutaj zastosować pewne wzorce projektowe, aby uniknąć problemów - członkowie klas mogą być modyfikowani, ponieważ C # przekazuje obiekty przez referencję (nie masz ByVal jak w VB ).

Wreszcie możesz użyć parametrów wyjściowych, ale ograniczyłbym użycie tego do scenariuszy, gdy masz tylko kilka (jak 3 lub mniej) parametrów - w przeciwnym razie rzeczy stają się brzydkie i trudne do utrzymania. Ponadto użycie parametrów wyjściowych może hamować sprawność, ponieważ sygnatura metody będzie musiała się zmieniać za każdym razem, gdy trzeba będzie coś dodać do wartości zwracanej, a zwracając instancję struktury lub klasy można dodawać elementy bez modyfikowania sygnatury metody.

Z architektonicznego punktu widzenia odradzałbym używanie par klucz-wartość lub słowników. Uważam, że ten styl kodowania wymaga „tajnej wiedzy” w kodzie używającym metody. Musi z góry wiedzieć, jakie będą klucze i co oznaczają wartości, a jeśli programista pracujący nad wewnętrzną implementacją zmieni sposób tworzenia słownika lub KVP, może łatwo stworzyć kaskadę awarii w całej aplikacji.

Kevin Hoffman
źródło
Możesz także rzucić, Exceptionjeśli druga wartość, którą chcesz zwrócić, jest rozłączna od pierwszej: na przykład, gdy chcesz zwrócić albo wartość udaną, albo rodzaj wartości nieudanej.
Cœur
21

Albo zwraca instancję klasy lub skorzystać z parametrów. Oto przykład naszych parametrów:

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

Nazwij to tak:

int i, j;
mymethod(out i, out j);
// i will be 20 and j will be 10
Keltex
źródło
3
Pamiętaj jednak, że tylko dlatego, że możesz, nie oznacza to, że powinieneś to zrobić. Jest to powszechnie akceptowane jako zła praktyka w .Net w większości przypadków.
Michael Meadows
4
Czy potrafisz wyjaśnić, dlaczego jest to zła praktyka?
Zo ma
Jest to zła praktyka w C / C ++. Problemem jest „programowanie przez efekt uboczny”: int GetLength (char * s) {int n = 0; while (s [n]! = '\ 0') n ++; s [1] = „X”; return (n); } int main () {char greeting [5] = {'H', 'e', ​​'l', 'p', '\ 0'}; int len ​​= GetLength (powitanie); cout << len << ":" << powitanie; // Wyjście: 5: HXlp} W języku C # musiałbyś napisać: int len ​​= GetLength (pozdrowienie referencyjne), co oznaczałoby dużą flagę ostrzegawczą „Hej, powitanie nie będzie takie samo po wywołaniu tego” i znacznie redukować błędy.
Dustin_00
19

Jest wiele sposobów; ale jeśli nie chcesz tworzyć nowego obiektu lub struktury lub czegoś takiego, możesz to zrobić jak poniżej po 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
źródło
13

W C # 7 dostępna jest nowa Tupleskładnia:

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

Możesz zwrócić to jako zapis:

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

Możesz także użyć nowej składni dekonstruktora:

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

Uważaj jednak na serializację, wszystko to jest cukier składniowy - w faktycznie skompilowanym kodzie będzie to Tuple<string, int>(zgodnie z przyjętą odpowiedzią ) zi Item1oraz Item2zamiast fooi bar. Oznacza to, że serializacja (lub deserializacja) użyje zamiast tego tych nazw właściwości.

W celu serializacji zadeklaruj klasę rekordów i zwróć ją.

Nowością w C # 7 jest ulepszona składnia outparametrów. Możesz teraz zadeklarować outwstawienie, które lepiej pasuje w niektórych kontekstach:

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

Jednak przeważnie będziesz tego używał we własnych bibliotekach .NET, a nie we własnych funkcjach.

Keith
źródło
Uwaga: w zależności od wersji docelowej .Net może być konieczne zainstalowanie pakietu Nuget System.ValueTuple.
Licht
miałem właśnie odpowiedzieć jak wyżej ;-)
Jeyara
12

Niektóre odpowiedzi sugerują użycie parametrów out, ale nie polecam tego, ponieważ nie działają one z metodami asynchronicznymi . Zobacz to, aby uzyskać więcej informacji.

Inne odpowiedzi podano przy użyciu Tuple, które również poleciłbym, ale przy użyciu nowej funkcji wprowadzonej w 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}.");

Więcej informacji można znaleźć tutaj .

Luis Teijon
źródło
11

Można to zrobić na kilka sposobów. Możesz użyć refparametrów:

int Foo(ref Bar bar) { }

To przekazuje odwołanie do funkcji, umożliwiając w ten sposób modyfikacji obiektu na stosie kodu wywołującego. Chociaż technicznie nie jest to wartość „zwrócona”, jest to sposób, aby funkcja zrobiła coś podobnego. W powyższym kodzie funkcja zwróci inti (potencjalnie) zmodyfikuje bar.

Innym podobnym podejściem jest użycie outparametru. outParametr jest identyczny z refparametrem z dodatkowym, kompilator egzekwowane reguły. Ta zasada mówi, że jeśli przekażesz outparametr do funkcji, funkcja ta musi ustawić swoją wartość przed zwróceniem. Oprócz tej reguły outparametr działa tak jak refparametr.

Ostatnim podejściem (i najlepszym w większości przypadków) jest stworzenie typu, który zawiera obie wartości i pozwala funkcji zwrócić:

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

FooBar Foo(Bar bar) { }

To końcowe podejście jest prostsze i łatwiejsze do odczytania i zrozumienia.

Andrew Hare
źródło
11

Nie, nie można zwrócić wielu wartości z funkcji w C # (dla wersji niższych niż C # 7), przynajmniej nie w sposób, w jaki można to zrobić w Pythonie.

Istnieje jednak kilka alternatyw:

Możesz zwrócić tablicę obiektu typu z wieloma wartościami, które chcesz w nim umieścić.

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

Możesz użyć outparametrów.

private string DoSomething(out string outparam1, out int outparam2)
{
    outparam1 = 'value2';
    outparam2 = 3;
    return 'value1';
}
Dustyburwell
źródło
10

W C # 4 będziesz mógł użyć wbudowanej obsługi krotek, aby łatwo sobie z tym poradzić.

W międzyczasie istnieją dwie opcje.

Po pierwsze, możesz użyć parametrów ref lub out, aby przypisać wartości do parametrów, które są przekazywane z powrotem do procedury wywołującej.

To wygląda jak:

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

Po drugie, możesz zawijać zwracane wartości do struktury lub klasy i przekazywać je z powrotem jako członków tej struktury. KeyValuePair działa dobrze dla 2 - dla więcej niż 2 potrzebujesz niestandardowej klasy lub struktury.

Reed Copsey
źródło
7

możesz wypróbować ten „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);

Wynik :

Wyjście: 1, 2

Rikin Patel
źródło
5

Klasy, struktury, kolekcje i tablice mogą zawierać wiele wartości. Parametry wyjściowe i odniesienia można również ustawić w funkcji. Zwracanie wielu wartości jest możliwe w językach dynamicznych i funkcjonalnych za pomocą krotek, ale nie w języku C #.

Jose Basilio
źródło
4

Istnieją głównie dwie metody. 1. Użyj parametrów out / ref 2. Zwróć tablicę obiektów

blitzkriegz
źródło
Istnieją również krotki i wiele wartości zwracanych jako cukier składniowy dla krotek.
ANeves
4

Oto podstawowe Twometody:

1) Użycie „ out” jako parametru Możesz użyć „out” zarówno dla wersji 4.0, jak i mniejszych.

Przykład „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();
      }
   }
}

Wynik:

Obszar prostokąta wynosi 20

Obwód prostokąta wynosi 18

* Uwaga: * outSłowo-klucz opisuje parametry, których rzeczywiste lokalizacje zmiennych są kopiowane na stos wywoływanej metody, gdzie te same lokalizacje można przepisać. Oznacza to, że metoda wywołująca uzyska dostęp do zmienionego parametru.

2) Tuple<T>

Przykład krotki:

Zwracanie wielu wartości DataType za pomocą 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);
    }
    }
}

Wynik

perl
java
c#
1
2
3

UWAGA: Użycie Tuple jest ważne od Framework 4.0 i nowszych . Tupletyp to class. Zostanie przydzielony w oddzielnej lokalizacji na zarządzanej stercie w pamięci. Po utworzeniu Tuplenie można zmienić jego wartości fields. To sprawia, że Tuplebardziej przypomina struct.

SHEKHAR SHETE
źródło
4
<--Return more statements like this you can --> 

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

Możesz otrzymać kod jak

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

Mam nadzieję, że to zadziała.

cruzier
źródło
3

Metoda przyjmująca delegata może dostarczyć rozmówcy wiele wartości. To zapożycza z mojej odpowiedzi tutaj i wykorzystuje trochę z zaakceptowanej odpowiedzi Hadasa .

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

Dzwoniący udostępniają lambda (lub nazwaną funkcję), a intellisense pomaga, kopiując nazwy zmiennych z delegata.

GetMultipleValues((upVotes, comments) =>
{
     Console.WriteLine($"This post has {upVotes} Up Votes and {comments} Comments.");
});
Scott Turner
źródło
2

Po prostu użyj w sposób OOP takiej klasy:

class div
{
    public int remainder;

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

Członek funkcji zwraca iloraz, którym najbardziej interesuje większość dzwoniących. Dodatkowo przechowuje resztę jako element danych, do którego później dzwoniący może łatwo uzyskać dostęp.

W ten sposób możesz uzyskać wiele dodatkowych „wartości zwracanych”, bardzo przydatne, jeśli wykonujesz połączenia z bazą danych lub sieciami, gdzie może być potrzebnych wiele komunikatów o błędach, ale tylko w przypadku wystąpienia błędu.

Podałem to rozwiązanie również w pytaniu C ++, do którego odnosi się OP.

Roland
źródło
2

W tym artykule możesz użyć trzech opcji, jak wspomniano powyżej.

KeyValuePair to najszybszy sposób.

obecnie jest na drugim.

Tuple jest najwolniejszy.

W każdym razie zależy to od tego, co jest najlepsze dla twojego scenariusza.

maoyang
źródło
2

Przyszła wersja C # będzie zawierać nazwane krotki. Obejrzyj sesję channel9 dla wersji demo https://channel9.msdn.com/Events/Build/2016/B889

Przejdź do 13:00 po krotkę. Pozwoli to na takie rzeczy jak:

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

int resultsum = Tally(numbers).sum

(niekompletny przykład z filmu)

Niels
źródło
2

Możesz użyć obiektu dynamicznego. Myślę, że ma lepszą czytelność niż 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
źródło
3
Tracisz sprawdzanie typu czasu kompilacji.
Micha Wiedenmann,
1

Sposoby na to:

1) KeyValuePair (najlepsza wydajność - 0,32 ns):

    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) Krotka - 5,40 ns:

    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 ns) lub ref 4) Stwórz własną klasę / strukturę

ns -> nanosekundy

Odniesienie: wielokrotne wartości zwrotne .

Adham Sabry
źródło
0

możesz tego spróbować

public IEnumerable<string> Get()
 {
     return new string[] { "value1", "value2" };
 }
NIEŚMIERTELNY
źródło
1
To tak naprawdę nie zwraca wielu wartości . Zwraca pojedynczą wartość kolekcji.
Matthew Haugen,
Ponadto, dlaczego nie użyć, yield return "value1"; yield return "value2";aby nie musieć jawnie tworzyć nowego string[]?
Thomas Flinkow
0

Możesz także użyć 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;
}
Ruan
źródło
-7

Szybka odpowiedź specjalnie dla typów tablicowych zwraca:

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

Za pomocą:

var results = SumAndSub(20, 5);
int sum = results[0];
int sub = results[1];
aliesTech
źródło
3
Co rozumiesz przez „programiści potrzebują czasu i niezapomnianych metod”?
Thomas Flinkow
2
użyłeś wyników [0] dwa razy. jest to symptom tego, co jest z tym nie tak
symbiont
1
Nie ma wątpliwości, że jest to niezapomniana odpowiedź
Luis Teijon