Praktyczny przykład zastosowania Tuple w .Net 4.0?

97

Widziałem Tuple wprowadzony w .Net 4, ale nie jestem w stanie sobie wyobrazić, gdzie można go użyć. Zawsze możemy stworzyć klasę niestandardową lub strukturę.

Amitabh
źródło
13
Może to dobre miejsce, aby zauważyć, że ten temat jest bardzo stary. Zobacz, co się stało w C # 7 !
maf-soft

Odpowiedzi:

83

O to właśnie chodzi - wygodniej jest nie tworzyć niestandardowej klasy lub struktury przez cały czas. Jest to ulepszenie Actionlub Func... możesz samemu tworzyć te typy, ale wygodnie jest, aby istniały w ramach.

tanascius
źródło
5
Prawdopodobnie warto zwrócić uwagę na MSDN : „Wszystkie publiczne statyczne elementy członkowskie tego typu są bezpieczne dla wątków. Nie ma gwarancji, że żadne elementy członkowskie instancji będą bezpieczne dla wątków.
Matt Borja,
Cóż, może więc projektanci języków powinni byli ułatwić tworzenie niestandardowych typów w locie? Więc moglibyśmy zachować tę samą składnię zamiast wprowadzać kolejną?
Thomas Eyde
@ThomasEyde, czyli dokładnie to, co robili przez ostatnie 15 lat. W ten sposób dodano elementy z treścią wyrażenia, które teraz cenią krotki. Klasy rekord stylu wąsko brakowało plecy cięcia w sierpniu (9 miesięcy przed upływem tego komentarza) dla tej wersji C # 7, i są prawdopodobnie wychodzi w C # 8. Należy również pamiętać, że wartość krotki oferują równości wartość gdzie zwykły starych klas nie . Wprowadzenie tego wszystkiego w 2002 roku wymagałoby przewidywania
Panagiotis Kanavos
co masz na myśli przez convenient not to make a custom class. Czy masz na myśli tworzenie niestandardowej instancji klasy za pomocą =new..()?
Alex
75

Dzięki krotkom można łatwo zaimplementować słownik dwuwymiarowy (lub n-wymiarowy, jeśli o to chodzi). Na przykład możesz użyć takiego słownika, aby zaimplementować mapowanie wymiany walut:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);

decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
MarioVW
źródło
Wolałbym raczej używać Enum dla skrótów krajów. Czy to jest możliwe?
Zack
1
Jasne, powinno działać bez problemów, ponieważ wyliczenia są typami wartości.
MarioVW
@MarioVW Można to również osiągnąć za pomocą wielowymiarowej tablicy. Jak krotka ma znaczenie?
Alex
26

W magazynie MSDN jest doskonały artykuł, który mówi o bólach brzucha i kwestiach projektowych, które doprowadziły do ​​dodania Tuple do BCL. Szczególnie interesujący jest wybór między typem wartości a typem referencyjnym.

Jak wyjaśnia artykuł, siłą napędową Tuple było tak wiele grup wewnątrz firmy Microsoft, które miały z tego pożytek, zespół F # z przodu. Chociaż nie zostało to wspomniane, uważam, że nowe słowo kluczowe „dynamiczne” w C # (i VB.NET) również miało z tym coś wspólnego, krotki są bardzo powszechne w językach dynamicznych.

Poza tym tworzenie własnego poco nie jest szczególnie lepsze, przynajmniej możesz nadać członkom lepszą nazwę.


AKTUALIZACJA: w związku z dużą rewizją w C # wersji 7, teraz otrzymuję dużo więcej miłości do składni. Wstępne ogłoszenie w tym poście na blogu .

Hans Passant
źródło
23

Oto mały przykład - załóżmy, że masz metodę, która musi wyszukać uchwyt użytkownika i adres e-mail, biorąc pod uwagę identyfikator użytkownika. Zawsze możesz utworzyć niestandardową klasę, która zawiera te dane, lub użyć parametru ref / out dla tych danych, lub możesz po prostu zwrócić krotkę i mieć ładną sygnaturę metody bez konieczności tworzenia nowego POCO.

public static void Main(string[] args)
{
    int userId = 0;
    Tuple<string, string> userData = GetUserData(userId);
}

public static Tuple<string, string> GetUserData(int userId)
{
    return new Tuple<string, string>("Hello", "World");
}
Tejs
źródło
10
Jest to jednak dobry przykład, który nie uzasadnia używania Tuple.
Amitabh
7
Krotka jest tutaj przyzwoicie dopasowana, ponieważ zwracasz różne wartości; ale krotka świeci bardziej, gdy zwracasz wiele wartości różnych typów .
Mark Rushakoff
21
Innym dobrym przykładem może być int.TryParse, ponieważ można wyeliminować parametr wyjściowy i zamiast tego użyć krotki. Więc możesz mieć Tuple<bool, T> TryParse<T>(string input)i zamiast używać parametru wyjściowego, otrzymujesz obie wartości z powrotem w krotce.
Tejs
3
w rzeczywistości dokładnie to się dzieje, gdy wywołujesz dowolną metodę TryParse z F #.
Joel Mueller
23

Użyłem krotki, aby rozwiązać problem 11 projektu Euler :

class Grid
{
    public static int[,] Cells = { { 08, 02, 22, // whole grid omitted

    public static IEnumerable<Tuple<int, int, int, int>> ToList()
    {
        // code converts grid to enumeration every possible set of 4 per rules
        // code omitted
    }
}

Teraz mogę rozwiązać cały problem za pomocą:

class Program
{
    static void Main(string[] args)
    {
        int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
        Console.WriteLine("Maximum product is {0}", product);
    }
}

I mógłby być stosowany typ niestandardowy dla tego, ale byłoby to wyglądało dokładnie tak, jak krotki .

Craig Stuntz
źródło
16

Składnia krotki w języku C # jest absurdalnie obszerna, więc deklarowanie krotek jest trudne. I nie ma dopasowywania wzorców, więc są również bolesne w użyciu.

Ale czasami potrzebujesz tylko grupowania obiektów ad hoc bez tworzenia dla niego klasy. Na przykład, powiedzmy, że chciałem zagregować listę, ale chciałem mieć dwie wartości zamiast jednej:

// sum and sum of squares at the same time
var x =
    Enumerable.Range(1, 100)
    .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));

Zamiast łączyć zbiór wartości w jeden wynik, rozwińmy pojedynczy wynik do zbioru wartości. Najłatwiejszym sposobem zapisania tej funkcji jest:

static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
    Tuple<T, State> res;
    while ((res = f(seed)) != null)
    {
        yield return res.Item1;
        seed = res.Item2;
    }
}

fkonwertuje stan na krotkę. Zwracamy pierwszą wartość z krotki i ustawiamy nasz nowy stan na drugą wartość. To pozwala nam zachować stan podczas obliczeń.

Używasz go jako takiego:

// return 0, 2, 3, 6, 8
var evens =
    Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
    .ToList();

// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
    Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
    .Take(10).ToList();

evensjest dość prosty, ale fibsjest trochę sprytniejszy. W staterzeczywistości jest to krotka, która zawiera odpowiednio fib (n-2) i fib (n-1).

Julia
źródło
4
+1 Tuple.Create jest przydatnym skrótem dlanew Tuple<Guid,string,...>
AaronLS,
@Juliet Jakie użycie słowa kluczowego State
irfandar
7

Nie podoba mi się ich nadużywanie, ponieważ tworzą kod, który nie wyjaśnia samego siebie, ale są niesamowite w implementacji kluczy złożonych w locie, ponieważ implementują IStructuralEquatable i IStructuralComparable (do użycia zarówno do wyszukiwania, jak i porządkowania cele).

I łączą wewnętrznie hashcody wszystkich swoich elementów; na przykład tutaj jest GetHashCode Tuple (pobrane z ILSpy):

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }
Notoriousxl
źródło
7

Krotki doskonale nadają się do wykonywania wielu asynchronicznych operacji we / wy na raz i zwracania wszystkich wartości razem. Oto przykłady robienia tego z Tuple i bez. Krotki mogą faktycznie uczynić twój kod bardziej przejrzystym!

Bez (paskudnego zagnieżdżenia!):

Task.Factory.StartNew(() => data.RetrieveServerNames())
    .ContinueWith(antecedent1 =>
        {
            if (!antecedent1.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
                Task.Factory.StartNew(() => data.RetrieveLogNames())
                    .ContinueWith(antecedent2 =>
                        {
                            if (antecedent2.IsFaulted)
                            {
                                LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
                                Task.Factory.StartNew(() => data.RetrieveEntryTypes())
                                    .ContinueWith(antecedent3 =>
                                        {
                                            if (!antecedent3.IsFaulted)
                                            {
                                                EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
                                            }
                                        });
                            }
                        });
            }
        });

Dzięki Tuple

Task.Factory.StartNew(() =>
    {
        List<string> serverNames = data.RetrieveServerNames();
        List<string> logNames = data.RetrieveLogNames();
        List<string> entryTypes = data.RetrieveEntryTypes();
        return Tuple.Create(serverNames, logNames, entryTypes);
    }).ContinueWith(antecedent =>
        {
            if (!antecedent.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
                LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
                EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
            }
        });

Jeśli korzystasz z funkcji anonimowego typu domniemanych zresztą wtedy nie robią kod mniej jasna za pomocą krotka. Ponowne dostrojenie krotki z metody? Moim skromnym zdaniem używaj oszczędnie, gdy klarowność kodu jest kluczowa. Wiem, że programowaniu funkcjonalnemu w C # trudno się oprzeć, ale musimy wziąć pod uwagę tych wszystkich starych, niezgrabnych programistów C # zorientowanych obiektowo.

AndyClaw
źródło
5

Krotki są intensywnie używane w językach funkcjonalnych, które mogą robić z nimi więcej rzeczy, teraz F # jest „oficjalnym” językiem .net, z którym można współpracować z C # i przekazywać je między kodem napisanym w dwóch językach.

Mant101
źródło
Krotki są również wbudowane w typy dla niektórych popularnych języków skryptowych, takich jak Python i Ruby (które mają również implementacje .Net dla interop ... IronPython / Ruby).
MutantNinjaCodeMonkey
5

Zwykle unikam Tuplew większości scenariuszy, ponieważ szkodzi to czytelności. Jest to jednak Tupleprzydatne, gdy trzeba pogrupować niepowiązane dane.

Na przykład załóżmy, że masz listę samochodów i miast, w których zostały zakupione:

Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle

Chcesz zsumować liczby dla każdego samochodu na miasto:

Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]

Aby to zrobić, tworzysz plik Dictionary. Masz kilka opcji:

  1. Utwórz plik Dictionary<string, Dictionary<string, int>>.
  2. Utwórz plik Dictionary<CarAndCity, int>.
  3. Utwórz plik Dictionary<Tuple<string, string>, int>.

Pierwsza opcja powoduje utratę czytelności. Będziesz musiał napisać dużo więcej kodu.

Druga opcja działa i jest zwięzła, ale samochód i miasto nie są tak naprawdę powiązane i prawdopodobnie nie należą do jednej klasy razem.

Trzecia opcja jest zwięzła i przejrzysta. To dobry użytek z Tuple.

John Kurlak
źródło
4

Kilka przykładów z mojej głowy:

  • Lokalizacja X i Y (i Z, jeśli chcesz)
  • a Szerokość i wysokość
  • Wszystko mierzone w czasie

Na przykład nie chciałbyś dołączać System.Drawing do aplikacji internetowej tylko po to, aby używać Point / PointF i Size / SizeF.

James Westgate
źródło
2
Tuplejest równie przydatny w sytuacjach krytycznych, jak Pointlub SomeVectormoże być przydatny podczas wykonywania operacji graficznych. Podkreśla dwie wartości, które są ze sobą bardzo powiązane. Często widzę właściwości o nazwach StartTime i EndTime podczas czytania kodu, ale nawet lepiej niż data, godzina i czas trwania, krotka zmusza cię do rozważenia obu wartości za każdym razem, gdy działasz w tym obszarze logiki biznesowej. Lub zwracanie „hej, dane się zmieniły (bool), oto dane (inny typ)” w ciężkim przetwarzaniu zamiast przechodzenia przez intensywne powiązania.
Léon Pelletier
3

Powinieneś być bardzo ostrożny podczas używania Tuplei prawdopodobnie dwa razy pomyśleć, zanim to zrobisz. Z mojego wcześniejszego doświadczenia dowiedziałem się, że używanie Tuplebardzo utrudnia czytanie i obsługę kodu w przyszłości. Jakiś czas temu musiałem naprawić kod, w którym krotki były używane prawie wszędzie. Zamiast myśleć o odpowiednich modelach obiektowych, używali po prostu krotek. To był koszmar ... czasami chciałem zabić gościa, który napisał kod ...

Nie chcę mówić, że nie powinieneś używać Tuplei to jest złe czy coś i jestem w stu procentach pewien, że są zadania, w których Tuplejest najlepszym kandydatem do użycia, ale prawdopodobnie powinieneś się jeszcze raz zastanowić, czy NAPRAWDĘ tego potrzebujesz ?

Mr. Pumpkin
źródło
1

Najlepszym zastosowaniem dla krotek, które znalazłem, jest zwrócenie więcej niż jednego typu obiektu z metody, wiesz, jakie będą typy obiektów i liczba, i nie jest to długa lista.

Inną prostą alternatywą byłoby użycie parametru „out”

private string MyMethod(out object)

lub tworzenie słownika

Dictionary<objectType1, objectType2>

Użycie krotki pozwala jednak uniknąć tworzenia obiektu „out” lub konieczności przeszukiwania hasła w słowniku;

sidjames
źródło
1

Właśnie znalazłem rozwiązanie jednego z moich problemów w Tuple. Jest to podobne do deklarowania klasy w zakresie metody, ale z leniwym deklarowaniem nazw jej pól. Operujesz na kolekcjach krotek, ich pojedynczych wystąpieniach, a następnie na podstawie swojej krotki tworzysz kolekcję typu anonimowego z wymaganymi nazwami pól. Pozwala to uniknąć tworzenia nowej klasy w tym celu.

Zadaniem jest napisanie odpowiedzi JSON z LINQ bez żadnych dodatkowych klas:

 //I select some roles from my ORM my with subrequest and save results to Tuple list
 var rolesWithUsers = (from role in roles
                       select new Tuple<string, int, int>(
                         role.RoleName, 
                         role.RoleId, 
                         usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
                      ));

 //Then I add some new element required element to this collection
 var tempResult = rolesWithUsers.ToList();
 tempResult.Add(new Tuple<string, int, int>(
                        "Empty", 
                         -1,
                         emptyRoleUsers.Count()
                      ));

 //And create a new anonimous class collection, based on my Tuple list
 tempResult.Select(item => new
            {
                GroupName = item.Item1,
                GroupId = item.Item2,
                Count = item.Item3
            });


 //And return it in JSON
 return new JavaScriptSerializer().Serialize(rolesWithUsers);

Oczywiście moglibyśmy to zrobić deklarując nową klasę dla moich grup, ale pomysł stworzenia takich anonimowych kolekcji bez deklarowania nowych klas.

Alex
źródło
1

Cóż, w moim przypadku musiałem użyć krotki, gdy dowiedziałem się, że nie możemy użyć parametru out w metodzie asynchronicznej. Przeczytaj o tym tutaj . Potrzebowałem też innego typu zwrotu. Więc zamiast tego użyłem krotki jako mojego typu zwracanego i oznaczyłem metodę jako asynchroniczną.

Przykładowy kod poniżej.

...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...

private async Tuple<string,int> GetUserDetails(int userId)
{
    return new Tuple<string,int>("Amogh",105);
    // Note that I can also use the existing helper method (Tuple.Create).
}

Przeczytaj więcej o Tuple tutaj . Mam nadzieję że to pomoże.

Amogh Natu
źródło
Zakładam, że nie chcesz zwrócić anonimowego typu lub tablicy (lub zawartości dynamicznej)
ozzy432836
0

Zmienianie kształtów obiektów, gdy trzeba je przesłać przez kabel lub przejść do innej warstwy aplikacji, a wiele obiektów zostaje połączonych w jedną:

Przykład:

var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();

Metoda rozszerzenia:

public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
    {
        var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
        var customerDetails = new CustomerDetails
        {
            FirstName = customerAndAddress.Item1.Name,
            LastName = customerAndAddress.Item1.Surname,
            Title = customerAndAddress.Item1.Title,
            Dob = customerAndAddress.Item1.Dob,
            EmailAddress = customerAndAddress.Item1.Email,
            Gender = customerAndAddress.Item1.Gender,
            PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
        };

        if (mainAddress != null)
        {
            customerDetails.AddressLine1 =
                !string.IsNullOrWhiteSpace(mainAddress.HouseName)
                    ? mainAddress.HouseName
                    : mainAddress.HouseNumber;
            customerDetails.AddressLine2 =
                !string.IsNullOrWhiteSpace(mainAddress.Street)
                    ? mainAddress.Street
                    : null;
            customerDetails.AddressLine3 =
                !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
            customerDetails.AddressLine4 =
                !string.IsNullOrWhiteSpace(mainAddress.County)
                    ? mainAddress.County
                    : null;
            customerDetails.PostCode = mainAddress.PostCode;
        }
...
        return customerDetails;
    }
Matas Vaitkevicius
źródło
0

Parametr out jest świetny, gdy istnieje tylko kilka wartości, które należy zwrócić, ale gdy zaczniesz napotykać 4, 5, 6 lub więcej wartości, które muszą zostać zwrócone, może stać się nieporęczny. Inną opcją zwracania wielu wartości jest utworzenie i zwrócenie klasy / struktury zdefiniowanej przez użytkownika lub użycie krotki w celu spakowania wszystkich wartości, które mają zostać zwrócone przez metodę.

Pierwsza opcja, wykorzystująca klasę / strukturę do zwracania wartości, jest prosta. Po prostu utwórz typ (w tym przykładzie jest to struktura) w następujący sposób:

public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}

Druga opcja, używając krotki, jest jeszcze bardziej eleganckim rozwiązaniem niż użycie obiektu zdefiniowanego przez użytkownika. Można utworzyć krotkę do przechowywania dowolnej liczby wartości różnych typów. Ponadto dane przechowywane w krotce są niezmienne; Po dodaniu danych do krotki za pomocą konstruktora lub statycznej metody Create nie można ich zmienić. Krotki mogą akceptować maksymalnie osiem oddzielnych wartości. Jeśli chcesz zwrócić więcej niż osiem wartości, będziesz musiał użyć specjalnej klasy krotki: Klasa krotki Tworząc krotkę z więcej niż ośmioma wartościami, nie można użyć statycznej metody Create - zamiast tego należy użyć konstruktora klasy. Oto jak można utworzyć krotkę składającą się z 10 wartości całkowitych:

var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));

Oczywiście możesz nadal dodawać więcej krotek na końcu każdej osadzonej krotki, tworząc krotkę o dowolnym rozmiarze.

Yashwanth Chowdary Kata
źródło
0

Tylko do prototypowania - krotki są bez znaczenia. Wygodne w użyciu, ale to tylko skrót! W przypadku prototypów - dobrze. Pamiętaj tylko, aby później usunąć ten kod.

Łatwe do napisania, trudne do odczytania. Nie ma widocznej przewagi nad klasami, klasami wewnętrznymi, klasami anonimowymi itp.

bunny1985
źródło
0

Cóż, wypróbowałem 3 sposoby rozwiązania tego samego problemu w C # 7 i znalazłem przypadek użycia dla krotek.

Praca z dynamicznymi danymi w projektach internetowych może czasami być uciążliwa podczas mapowania itp.

Podoba mi się sposób, w jaki krotka automatycznie mapowała się na element1, element2, elementN, co wydaje mi się bardziej niezawodne niż używanie indeksów tablicowych, w których możesz zostać złapany na elemencie spoza indeksu lub używając typu anonimowego, w którym możesz błędnie napisać nazwę właściwości.

Wydaje się, że DTO zostało stworzone za darmo tylko przy użyciu krotki i mogę uzyskać dostęp do wszystkich właściwości za pomocą itemN, co bardziej przypomina statyczne pisanie bez konieczności tworzenia oddzielnego DTO w tym celu.

using System;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            var tuple = GetTuple();
            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
            Console.WriteLine(tuple.Item3);
            Console.WriteLine(tuple);

            Console.WriteLine("---");

            var dyn = GetDynamic();
            Console.WriteLine(dyn.First);
            Console.WriteLine(dyn.Last);
            Console.WriteLine(dyn.Age);
            Console.WriteLine(dyn);

            Console.WriteLine("---");

            var arr = GetArray();
            Console.WriteLine(arr[0]);
            Console.WriteLine(arr[1]);
            Console.WriteLine(arr[2]);
            Console.WriteLine(arr);

            Console.Read();

            (string, string, int) GetTuple()
            {
                return ("John", "Connor", 1);
            }

            dynamic GetDynamic()
            {
                return new { First = "John", Last = "Connor", Age = 1 };
            }

            dynamic[] GetArray()
            {
                return new dynamic[] { "John", "Connor", 1 };
            }
        }
    }
}
ozzy432836
źródło