Kembali beberapa nilai ke pemanggil metode


440

Saya membaca versi C ++ dari pertanyaan ini tetapi tidak terlalu memahaminya.

Bisakah seseorang tolong jelaskan jika itu bisa dilakukan dan bagaimana caranya?

Jawaban:


610

Di C # 7 dan di atas, lihat jawaban ini .

Dalam versi sebelumnya, Anda dapat menggunakan Tuple .NET 4.0 + :

Sebagai contoh:

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

Tuple dengan dua nilai memiliki Item1dan Item2sebagai properti.


8
Akan sangat bagus jika bukan Item1, Item2 dan seterusnya orang dapat menggunakan nilai output bernama. C # 7 mungkin akan menyediakannya .
Sнаđошƒаӽ

1
@ Sнаđошƒаӽ benar sekali, ini diharapkan akan didukung dalam C # 7.0 mendatang menggunakan sintaksis seperti: public (int sum, int count) GetMultipleValues() { return (1, 2); }Contoh ini diambil dari contoh topik Dokumentasi kami tentang ini .
Jeppe Stig Nielsen

436

Sekarang setelah C # 7 telah dirilis, Anda dapat menggunakan sintaks Tuples yang baru disertakan

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

yang kemudian bisa digunakan seperti ini:

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

Anda juga dapat memberikan nama ke elemen Anda (jadi mereka bukan "Item1", "Item2" dll). Anda dapat melakukannya dengan menambahkan nama ke tanda tangan atau metode pengembalian:

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

atau

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

Mereka juga dapat didekonstruksi, yang merupakan fitur baru yang cukup bagus:

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

Lihat tautan ini untuk melihat lebih banyak contoh tentang apa yang dapat dilakukan :)


11
Jika Anda menargetkan sesuatu lebih awal dari .NET Framework 4.7 atau .NET Core 2.0, Anda harus menginstal paket NuGet .
Phil

1
Untuk mendapatkan hasil, Anda dapat melakukan: "var result = LookupName (5); Console.WriteLine (result.middle)".
alansiqueira27

204

Anda dapat menggunakan tiga cara berbeda

1. parameter ref / out

menggunakan referensi:

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

menggunakan:

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

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

menggunakan kelas:

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

Kelas 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 Tuples

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
Hanya untuk keberanian saya sendiri, yang akan Anda katakan tercepat dan 'praktik terbaik'?
Netferret

contoh terbaik untuk 'using struct' :)
SHEKHAR SHETE

1
menyarankan untuk menambahkan sintaks c # 7 (dan lebih banyak orang memilih ini :))
twomm

Untuk info Anda, kesalahan ketik kecil (tidak relevan): Di solusi struct / kelas Anda menggabungkan menambahkan / mengalikan.
Szak1

Saya tidak tahu sintaks C # Tuple adalah sesuatu! Pelajari sesuatu yang baru bahkan setelah bertahun-tahun!
jhaagsma

75

Anda tidak dapat melakukan ini dalam C #. Yang dapat Anda lakukan adalah memilikiout parameter atau mengembalikan kelas Anda sendiri (atau struct jika Anda ingin itu tidak berubah).

Menggunakan parameter keluar
public int GetDay(DateTime date, out string name)
{
  // ...
}
Menggunakan kelas khusus (atau struct)
public DayOfWeek GetDay(DateTime date)
{
  // ...
}

public class DayOfWeek
{
  public int Day { get; set; }
  public string Name { get; set; }
}

24
Alternatif dalam hal ini adalah dengan menggunakan struct, bukan kelas untuk tipe kembali. Jika nilai kembali adalah stateless dan sementara, struct adalah pilihan yang lebih baik.
Michael Meadows

1
Ini tidak mungkin dengan asyncmetode. Tupleadalah cara untuk pergi. (Saya menggunakan outparameter dalam operasi sinkron; mereka memang berguna dalam kasus-kasus itu.)
Codefun64

5
Ini sekarang mungkin di C # 7: (int, int) Metode () {return (1, 2); }
Spook

4
Jawaban perlu diperbarui, ini menjadi salah dengan versi c # terbaru. akan mengubah downvote menjadi upvote jika diperbarui.
whitneyland

Bekerja pada basis kode lama, mengembalikan kelas khusus adalah pendekatan yang solid bagi saya.
Brant

38

Jika Anda bermaksud mengembalikan beberapa nilai, Anda dapat mengembalikan kelas / struct yang berisi nilai yang ingin Anda kembalikan, atau menggunakan kata kunci "out" pada parameter Anda, seperti:

public void Foo(int input, out int output1, out string output2, out string errors) {
    // set out parameters inside function
}

2
Saya tidak berpikir itu baik untuk menggunakan "keluar" atau "ref" - karena dapat sepenuhnya diganti dengan nilai yang dikembalikan dari tipe kelas Anda sendiri. Anda lihat, jika menggunakan "ref", bagaimana cara menetapkan parameter tersebut? (Itu hanya tergantung pada cara kode di dalam). Jika dalam tubuh fungsi, penulis telah "baru" contoh ke parameter dengan "ref", ini berarti Anda bisa memberikan nilai "nullable" di sana. Lainnya tidak. Jadi itu sedikit ambisius. Dan kami memiliki cara yang lebih baik (1. Mengembalikan kelas yang Anda miliki, 2. Turple).

33

Poster sebelumnya benar. Anda tidak dapat mengembalikan beberapa nilai dari metode C #. Namun, Anda memiliki beberapa opsi:

  • Kembalikan struktur yang berisi banyak anggota
  • Kembalikan instance kelas
  • Gunakan parameter output (menggunakan kata kunci out atau ref )
  • Gunakan kamus atau pasangan nilai kunci sebagai output

Pro dan kontra di sini seringkali sulit dipecahkan. Jika Anda mengembalikan struktur, pastikan itu kecil karena struct adalah tipe nilai dan diteruskan pada stack. Jika Anda mengembalikan instance kelas, ada beberapa pola desain di sini yang mungkin ingin Anda gunakan untuk menghindari masalah - anggota kelas dapat dimodifikasi karena C # melewati objek dengan referensi (Anda tidak memiliki ByVal seperti yang Anda lakukan di VB ).

Akhirnya Anda dapat menggunakan parameter output tetapi saya akan membatasi penggunaan ini untuk skenario ketika Anda hanya memiliki pasangan (seperti 3 atau kurang) parameter - jika tidak semuanya menjadi jelek dan sulit untuk dipelihara. Selain itu, penggunaan parameter output dapat menjadi penghambat kelincahan karena tanda tangan metode Anda harus berubah setiap kali Anda perlu menambahkan sesuatu ke nilai kembali sedangkan mengembalikan contoh struct atau kelas Anda dapat menambahkan anggota tanpa memodifikasi tanda tangan metode.

Dari sudut pandang arsitektur, saya akan merekomendasikan untuk tidak menggunakan pasangan kunci-nilai atau kamus. Saya menemukan gaya pengkodean ini membutuhkan "pengetahuan rahasia" dalam kode yang menggunakan metode ini. Ia harus mengetahui sebelumnya apa yang akan menjadi kunci dan apa nilai-nilai artinya dan jika pengembang bekerja pada implementasi internal mengubah cara kamus atau KVP dibuat, itu bisa dengan mudah membuat kaskade kegagalan di seluruh aplikasi.


Dan Anda juga dapat melempar Exceptionjika nilai kedua yang ingin Anda kembalikan terpisah dari yang pertama: seperti ketika Anda ingin mengembalikan salah satu jenis nilai sukses, atau semacam nilai tidak berhasil.
Cœur

21

Anda baik mengembalikan contoh kelas atau menggunakan keluar parameter. Berikut ini contoh parameter keluar:

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

Sebut saja seperti ini:

int i, j;
mymethod(out i, out j);
// i will be 20 and j will be 10

3
Ingat, meskipun itu hanya karena Anda bisa, bukan berarti Anda harus melakukan ini. Ini diterima secara luas sebagai praktik buruk di .Net dalam banyak kasus.
Michael Meadows

4
Bisakah Anda menguraikan mengapa ini praktik yang buruk?
Zo Memiliki

Ini praktik buruk di C / C ++. Masalahnya adalah "pemrograman dengan efek samping": int GetLength (char * s) {int n = 0; while (s [n]! = '\ 0') n ++; s [1] = 'X'; return (n); } int main () {char salam [5] = {'H', 'e', ​​'l', 'p', '\ 0'}; int len ​​= GetLength (salam); cout << len << ":" << salam; // Output: 5: HXlp} Dalam C # Anda harus menulis: int len ​​= GetLength (ref greeting) Yang mana akan menandakan bendera peringatan besar "Hei, salam tidak akan sama setelah Anda menyebutnya" dan sangat mengurangi bug.
Dustin_00

19

Ada banyak cara; tetapi jika Anda tidak ingin membuat Obyek atau struktur baru atau sesuatu seperti ini, Anda dapat melakukannya seperti di bawah ini setelah 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");

    }

13

Di C # 7 Ada Tuplesintaks baru :

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

Anda dapat mengembalikan ini sebagai catatan:

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

Anda juga dapat menggunakan sintaks dekonstruktor baru:

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

Berhati-hatilah dengan serialisasi, semua ini adalah gula sintaksis - dalam kode terkompilasi yang sebenarnya, ini akan menjadi Tuple<string, int>( sesuai jawaban yang diterima ) dengan Item1dan Item2bukannya foodan bar. Itu berarti bahwa serialisasi (atau deserialisation) akan menggunakan nama-nama properti sebagai gantinya.

Jadi, untuk serialisasi mendeklarasikan kelas rekaman dan mengembalikannya sebagai gantinya.

Juga baru di C # 7 adalah sintaks yang ditingkatkan untuk outparameter. Anda sekarang dapat mendeklarasikan outinline, yang lebih cocok dalam beberapa konteks:

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

Namun, sebagian besar Anda akan menggunakan ini di perpustakaan .NET sendiri, bukan di fungsi Anda sendiri.


Harap perhatikan bahwa, tergantung pada versi .Net apa yang Anda targetkan, Anda mungkin perlu menginstal paket Nuget System.ValueTuple.
Licht

saya akan menjawab seperti di atas ;-)
Jeyara

12

Beberapa jawaban menyarankan untuk menggunakan parameter tetapi saya sarankan tidak menggunakan ini karena mereka tidak bekerja dengan metode async . Lihat ini untuk informasi lebih lanjut.

Jawaban lain dinyatakan menggunakan Tuple, yang saya sarankan juga tetapi menggunakan fitur baru yang diperkenalkan di 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}.");

Informasi lebih lanjut dapat ditemukan di sini .


11

Ada beberapa cara untuk melakukan ini. Anda dapat menggunakan refparameter:

int Foo(ref Bar bar) { }

Ini meneruskan referensi ke fungsi sehingga memungkinkan fungsi untuk mengubah objek dalam tumpukan kode panggilan. Meskipun ini bukan nilai "kembali" secara teknis, ini adalah cara agar fungsi melakukan hal yang serupa. Dalam kode di atas fungsi akan mengembalikan intdan (berpotensi) memodifikasi bar.

Pendekatan lain yang serupa adalah dengan menggunakan outparameter. Sebuah outparameter identik dengan refparameter dengan tambahan compiler ditegakkan aturan,. Aturan ini adalah bahwa jika Anda melewatkan outparameter ke suatu fungsi, fungsi itu diperlukan untuk mengatur nilainya sebelum kembali. Selain aturan itu, outparameter berfungsi seperti refparameter.

Pendekatan terakhir (dan yang terbaik dalam banyak kasus) adalah membuat tipe yang merangkum kedua nilai dan memungkinkan fungsi mengembalikannya:

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

FooBar Foo(Bar bar) { }

Pendekatan akhir ini lebih sederhana dan lebih mudah dibaca dan dipahami.


11

Tidak, Anda tidak dapat mengembalikan beberapa nilai dari fungsi dalam C # (untuk versi yang lebih rendah dari C # 7), setidaknya tidak dengan cara yang dapat Anda lakukan dengan Python.

Namun, ada beberapa alternatif:

Anda bisa mengembalikan array tipe objek dengan beberapa nilai yang Anda inginkan di dalamnya.

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

Anda dapat menggunakan outparameter.

private string DoSomething(out string outparam1, out int outparam2)
{
    outparam1 = 'value2';
    outparam2 = 3;
    return 'value1';
}

10

Di C # 4, Anda akan dapat menggunakan dukungan bawaan untuk tupel untuk menangani ini dengan mudah.

Sementara itu, ada dua opsi.

Pertama, Anda dapat menggunakan parameter ref atau out untuk menetapkan nilai ke parameter Anda, yang akan diteruskan kembali ke rutinitas pemanggilan.

Ini terlihat seperti:

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

Kedua, Anda bisa membungkus nilai pengembalian Anda ke dalam struktur atau kelas, dan meneruskannya kembali sebagai anggota struktur itu. KeyValuePair berfungsi dengan baik untuk 2 - untuk lebih dari 2 Anda membutuhkan kelas atau struct khusus.


7

Anda dapat mencoba "KeyValuePair" ini

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


var numbers = GetNumbers();

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

Keluaran:

Output: 1, 2


5

Kelas, Struktur, Koleksi dan Array dapat berisi beberapa nilai. Parameter keluaran dan referensi juga dapat diatur dalam suatu fungsi. Mengembalikan beberapa nilai dimungkinkan dalam bahasa yang dinamis dan fungsional dengan menggunakan tupel, tetapi tidak dalam C #.


4

Terutama ada dua metode. 1. Gunakan parameter out / ref 2. Kembalikan Array objek


Ada juga tupel, dan beberapa nilai pengembalian sebagai gula sintaksis untuk tupel.
ANeves

4

Berikut adalah Twometode dasar :

1) Penggunaan ' out' sebagai parameter Anda dapat menggunakan 'keluar' untuk versi 4.0 dan minor juga.

Contoh 'keluar':

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

Keluaran:

Luas Persegi Panjang adalah 20

Perimeter Persegi Panjang adalah 18

* Catatan: * out-keyword menjelaskan parameter yang lokasi variabel aktualnya disalin ke tumpukan metode yang dipanggil, di mana lokasi yang sama dapat ditulis ulang. Ini berarti bahwa metode panggilan akan mengakses parameter yang diubah.

2) Tuple<T>

Contoh Tuple:

Mengembalikan beberapa nilai Tipe Data menggunakan 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);
    }
    }
}

Keluaran

perl
java
c#
1
2
3

CATATAN: Penggunaan Tuple valid dari Framework 4.0 dan di atasnya . Tupletipe adalah a class. Ini akan dialokasikan di lokasi terpisah pada tumpukan yang dikelola dalam memori. Setelah Anda membuat Tuple, Anda tidak dapat mengubah nilai-nilainya fields. Ini membuat Tuplelebih seperti a struct.


4
<--Return more statements like this you can --> 

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

Anda dapat menerima kode seperti

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

Saya harap ini berhasil.


3

Metode mengambil delegasi dapat memberikan beberapa nilai kepada pemanggil. Ini meminjam dari jawaban saya di sini dan menggunakan sedikit dari jawaban Hadas yang diterima .

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

Penelepon menyediakan lambda (atau fungsi bernama) dan intellisense membantu dengan menyalin nama-nama variabel dari delegasi.

GetMultipleValues((upVotes, comments) =>
{
     Console.WriteLine($"This post has {upVotes} Up Votes and {comments} Comments.");
});

2

Cukup gunakan dengan cara OOP kelas seperti ini:

class div
{
    public int remainder;

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

Anggota fungsi mengembalikan hasil bagi sebagian besar penelepon yang terutama tertarik. Selain itu, anggota fungsi menyimpan sisanya sebagai anggota data, yang mudah diakses oleh penelepon sesudahnya.

Dengan cara ini Anda dapat memiliki banyak "nilai pengembalian" tambahan, sangat berguna jika Anda menerapkan panggilan basis data atau jaringan, di mana banyak pesan kesalahan mungkin diperlukan tetapi hanya jika terjadi kesalahan.

Saya memasukkan solusi ini juga dalam pertanyaan C ++ yang dirujuk OP.


2

Dari artikel ini , Anda dapat menggunakan tiga opsi seperti tulisan di atas.

KeyValuePair adalah cara tercepat.

keluar di yang kedua.

Tuple adalah yang paling lambat.

Bagaimanapun, ini tergantung pada apa yang terbaik untuk skenario Anda.


2

Versi C # yang akan datang akan memasukkan tuple bernama. Lihatlah sesi channel9 ini untuk demo https://channel9.msdn.com/Events/Build/2016/B889

Lompat ke 13:00 untuk hal-hal tuple. Ini akan memungkinkan hal-hal seperti:

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

int resultsum = Tally(numbers).sum

(contoh tidak lengkap dari video)


2

Anda bisa menggunakan objek dinamis. Saya pikir ini memiliki keterbacaan yang lebih baik daripada 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;
}

3
Anda kehilangan waktu pemeriksaan jenis kompilasi.
Micha Wiedenmann

1

Cara melakukannya:

1) KeyValuePair (Kinerja Terbaik - 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) Tuple - 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) keluar (1,64 ns) atau ref 4) Buat kelas / struct khusus Anda sendiri

ns -> nanodetik

Referensi: beberapa-return-nilai .


0

Anda bisa mencoba ini

public IEnumerable<string> Get()
 {
     return new string[] { "value1", "value2" };
 }

1
Ini tidak benar-benar mengembalikan beberapa nilai . Ini mengembalikan nilai koleksi tunggal.
Matthew Haugen

Juga, mengapa tidak menggunakan yield return "value1"; yield return "value2";karena tidak harus secara eksplisit membuat yang baru string[]?
Thomas Flinkow

0

Anda juga dapat menggunakan 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

Jawaban cepat khusus untuk pengembalian tipe array:

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

Menggunakan:

var results = SumAndSub(20, 5);
int sum = results[0];
int sub = results[1];

3
Apa yang Anda maksud dengan "programmer membutuhkan waktu & metode yang tidak dapat diampuni"?
Thomas Flinkow

2
Anda menggunakan hasil [0] dua kali. ini adalah simpati dari apa yang salah dengan ini
symbiont

1
Tidak ada keraguan tentang ini adalah Jawaban yang Tidak Terlupakan
Luis Teijon
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.