Metoda c # z nieograniczoną liczbą parametrów czy metoda z tablicą lub listą?

21

Niedawno dowiedziałem się, że możesz stworzyć metodę o nieograniczonych parametrach, na przykład:

SomeMethod(params int[] numbers);

ale moje pytanie brzmi: jaka jest różnica między tym a tworzeniem metody, która otrzymuje listę lub tablicę?

SomeMethod(int[] numbers);
SomeMethod(List<int> numbers);

może ma to jakiś wpływ na wydajność? Nie do końca rozumiem lub nie widzę, w jaki sposób wolisz ten z nieograniczonymi parametrami.

Szybkie wyszukiwanie w Google nie pomogło, mam nadzieję, że możesz mi pomóc.

Ricardo Sanchez Santos
źródło
Oprócz mojej poniższej odpowiedzi wymagaparams również, aby typ argumentu był tablicą. Jeśli potrzebujesz zużyć kolekcję inną niż tablica, bardziej sensowne może być podanie paramszamiast niej argumentu.
digitlworld
1
@digitlworld: Jeśli ten temat Cię interesuje, odwiedź github.com/dotnet/csharplang/issues/179 w celu dyskusji.
Eric Lippert
Spójrz na podpis i / lub wdrożenie Console.Write.
3Dave

Odpowiedzi:

27

Jaka jest różnica między tym a tworzeniem metody, która otrzymuje listę lub tablicę?

Różnica pomiędzy

void M(params int[] x)

i

void N(int[] x)

jest to, że M można nazwać tak:

M(1, 2, 3)

lub tak:

M(new int[] { 1, 2, 3 });

ale N można wywoływać tylko w drugi sposób, a nie w pierwszy sposób.

może ma to jakiś wpływ na wydajność?

Wpływ na wydajność jest taki, że niezależnie od tego, czy wywołujesz Mw pierwszy, czy w drugi sposób, tak czy inaczej, utworzysz tablicę. Utworzenie tablicy ma wpływ na wydajność, ponieważ zajmuje zarówno czas, jak i pamięć. Pamiętaj, że wpływ na wydajność należy mierzyć w stosunku do celów wydajności; jest mało prawdopodobne, aby koszt stworzenia dodatkowej tablicy był czynnikiem bramkowym, który jest różnicą między sukcesem a porażką na rynku.

Nie do końca rozumiem lub nie widzę, w jaki sposób wolisz ten z nieograniczonymi parametrami.

Jest to czysta i całkowicie wygoda dla autora kodu, który wywołuje metodę; jest po prostu krótszy i łatwiejszy do napisania

M(1, 2, 3);

zamiast pisać

M(new int[] { 1, 2, 3 });

Zapisuje tylko kilka naciśnięć klawiszy po stronie dzwoniącego. To wszystko.

Kilka pytań, które nie zadałeś, ale być może chciałbyś poznać odpowiedź na:

Jak nazywa się ta funkcja?

Metody pozwalające na przekazywanie zmiennej liczby argumentów po stronie wywołującej nazywane są variadic . Metody Params są sposobem, w jaki C # implementuje metody variadic.

Jak działa rozdzielczość przeciążenia w przypadku metody variadic?

W obliczu problemu rozwiązania problemu z przeciążeniem, C # rozważy zarówno formy „normalną”, jak i „rozwiniętą”, a forma „normalna” zawsze wygrywa, jeśli oba mają zastosowanie. Rozważmy na przykład:

void P(params object[] x){}

i mamy telefon

P(null);

Istnieją dwie możliwe możliwości. W „normalnej” formie wywołujemy Pi przekazujemy zerowe odwołanie do tablicy. W formie „rozszerzonej” dzwonimy P(new object[] { null }). W takim przypadku wygrywa normalna postać. Gdybyśmy mieli połączenie, P(null, null)wówczas normalna forma nie ma zastosowania, a forma rozszerzona wygrywa domyślnie.

Wyzwanie : załóżmy, że mamy var s = new[] { "hello" };telefon P(s);. Opisz, co dzieje się na stronie połączenia i dlaczego. Możesz być zaskoczony!

Wyzwanie : Załóżmy, że mamy jedno void P(object x){}i drugie void P(params object[] x){}. Co robi P(null)i dlaczego?

Wyzwanie : Załóżmy, że mamy jedno void M(string x){}i drugie void M(params string[] x){}. Co robi M(null)i dlaczego? Czym różni się to od poprzedniego przypadku?

Eric Lippert
źródło
Ch2: P(null)vs. P((object)null)vs. P((object[])null)- gdzie mogę znaleźć wyjaśnienie tej różnicy? Wydaje się, że nullmiał jakiś specjalny typ , który konwertuje na tablicę zamiast object ( P(null)), ale znajdź konwersję na ciąg znaków lub tablicę ciągów niejednoznacznych ( M(null)) ... co wydaje się bardzo dziwne, spodziewałby się, że będzie albo niejednoznaczny w obu przypadkach, albo wybierze wersja z jednym argumentem (której nie ma). Ale myślę, że bardziej chodzi o to, by paramsbyć jakoś ogólnym , jak na przykład uniwersalne odwołanie ( &&) w szablonach C ++, a zatem lepiej pasować (w Ch2, a nie w Ch3).
firda
@firda: Możesz znaleźć wyjaśnienie w specyfikacji C #. Powodem tej dziwności jest: wartość null jest konwertowana na obiekt, ciąg, obiekt [] i ciąg []. Tak więc pytanie, jakie należy postawić na rozwiązanie problemu przeciążenia, brzmi: która konwersja jest najlepsza ? Reguła kontrolna w tym przypadku jest specyficzna, jest lepsza niż ogólna . Jak rozpoznać, który typ jest bardziej szczegółowy? Zasada jest taka: wszystkie żyrafy są zwierzętami, ale nie wszystkie zwierzęta są żyrafami, dlatego żyrafa jest bardziej specyficzna niż zwierzę. Wszystkie object[]object, ale nie wszystkie objectobject[], dlatego object[]są bardziej szczegółowe.
Eric Lippert
@firda: Teraz już wiesz, dlaczego ciągi są niejednoznaczne. NIE jest prawdą, że „wszyscy string[]string”. W rzeczywistości NIE string[]stringi NIE stringstring[], więc stringnie jest bardziej konkretny ani bardziej ogólny niż string[], i pojawia się błąd dwuznaczności, gdy prosi się o wybór.
Eric Lippert
Wszystkie object[]object... object[]są bardziej szczegółowe, więc P(null)chodzi o bardziej konkretną tablicę, dzięki, tego mi brakowało. Znaleziono kilka reguł tutaj: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
firda
5

Właśnie zrobiłem mały prototyp. Wydaje się, że odpowiedzią paramsjest po prostu cukier składniowy do przekazywania w tablicy. To naprawdę nie jest niespodzianka. Stworzyłem dwie wersje tej samej metody, w których jedyną różnicą jest słowo kluczowe „params”. IL wygenerowana dla obu była identyczna, z tą różnicą, że System.ParamArrayAttributezastosowano ją do paramswersji.

Co więcej, IL wygenerowana w miejscu wywołania była również taka sama między moim wywołaniem metody z ręcznym zadeklarowaniem new int[]a wywołaniem metody za pomocą paramsargumentów.

Tak więc odpowiedzią wydaje się być „wygoda”. Wydaje się, że nie ma różnicy w wydajności. Zamiast tego możesz wywołać paramsfunkcję z tablicą, więc nie jest to również zaskakujące. Sprowadza się to do tego, czy konsumentowi twojej metody łatwiej jest wywołać ją z dowolną liczbą parametrów (np. someMethod(1, 2, 3)), Niż zawsze najpierw tworzyć kolekcję (np someMethod(new List<int>() { 1, 2, 3 } ).).

digitlworld
źródło
4

Funkcja nieograniczonej liczby parametrów oferuje następujące korzyści w wielu scenariuszach:

  1. Luźne powiązanie
  2. Zwiększona możliwość ponownego użycia
  3. Lepsza ogólna wydajność aplikacji

Oto przykład, w którym opcja nieograniczonej liczby parametrów jest doskonałym wyborem

Weź pod uwagę, że należy zbudować aplikację do wysyłania wiadomości e-mail.

Funkcja wysyłająca wiadomość e-mail musi obsługiwać jedną lub wiele wartości pól „Do”, „CC” i „BCC”.

Jeśli typy parametrów są ustawione jako tablice lub listy dla wszystkich pól (Do, CC, BCC), wówczas funkcja wywołująca będzie zmuszona poradzić sobie z całą złożonością zdefiniowania 3 tablic lub list w celu wywołania funkcji nadawcy wiadomości e-mail .

Nawet jeśli dzwoniący chce wysłać wiadomość e-mail na tylko jeden adres, funkcja nadawcy wiadomości e-mail zmusi ją do zdefiniowania i wysłania 3 różnych tablic jako parametrów.

Jeśli funkcja nadawcy wiadomości e-mail przyjmuje podejście z nieograniczoną liczbą par, funkcja dzwoniącego nie musi zajmować się całą złożonością.

Podejście do nieograniczonych parametrów przyczynia się do lepszego działania aplikacji w czasie wykonywania, unikając tworzenia tablic lub list tam, gdzie jest to niepotrzebne.

Gopinath
źródło
2

Z nieefektywnego stylu, paramssłowo kluczowe jest naprawdę miłe, gdy chcesz wysłać opcjonalną listę parametrów.

Osobiście korzystałbym, paramsgdy mój kod był podobny

SomeMethod('Option1', 'Option17');

void SomeMethod(params string[] options)
{
    foreach(var option in options)
    {
        switch(option): ...
    }
}

Zaletą tego jest to, że mogę korzystać z tej metody wszędzie, bez konieczności tworzenia tablicy lub listy za każdym razem.

Chciałbym użyć arraylub listgdy wiem, że zawsze przekażę tej funkcji zestaw danych, które już są zebrane razem

List<User> users = GetUsersFromDB();
SomeMethod(users);

Widzę zaletę paramselastyczności, którą dodaje. Może to mieć stosunkowo niewielki wpływ na kod, ale nadal jest to miłe narzędzie.

Sok Z Oliwek
źródło
1

Konwencja wywoływania jest inna. Na przykład ...

public class Test
{
    public void Method1( params int[] nums )
    {
        nums.ToList().ForEach( n => Console.WriteLine(n) );
    }

    public void Method2( List<int> nums )
    {
        nums.ForEach( n  => Console.WriteLine(n) );
    }   
}

void Main()
{   
    var t = new Test();
    t.Method1( 1, 2, 3, 4 );
    t.Method2( new List<int>() { 1, 2, 3, 4 } );
}

W pierwszym przypadku możesz przekazać do metody tyle ints, ile osobnych parametrów. W drugim przypadku musisz utworzyć instancję listy i przekazać ją.

JP Alioto
źródło