Properti atau pengindeks tidak boleh dikirimkan sebagai parameter keluar atau ref

90

Saya mendapatkan kesalahan di atas dan tidak dapat menyelesaikannya. Saya mencari di Google sedikit tetapi tidak bisa menyingkirkannya.

Skenario:

Saya memiliki kelas BudgetAllocate yang propertinya adalah anggaran yang berjenis ganda.

Di dataAccessLayer saya,

Di salah satu kelas saya, saya mencoba melakukan ini:

double.TryParse(objReader[i].ToString(), out bd.Budget);

Yang melempar kesalahan ini:

Properti atau pengindeks tidak boleh dikirimkan sebagai parameter keluar atau ref pada waktu kompilasi.

Saya bahkan mencoba ini:

double.TryParse(objReader[i].ToString().Equals(DBNull.Value) ? "" : objReader[i].ToString(), out bd.Budget);

Segala sesuatu yang lain berfungsi dengan baik dan referensi antar lapisan ada.

Pratik
sumber
Di bd.Budget bd adalah objek dari kelas BudgetAllocate. Maaf saya lupa.
Pratik
1
kemungkinan duplikat Mengakses properti melalui parameter tipe Generik
Chris Moschini
1
Kemungkinan duplikat properti Melewati dengan referensi di C #
Legolas
Baru saja menemukan ini bekerja dengan tipe pengguna yang memiliki bidang yang ditentukan yang saya harapkan DataGriduntuk diisi dari kemudian datang untuk mempelajarinya hanya secara otomatis dengan properti. Beralih ke properti merusak beberapa parameter ref yang saya gunakan di bidang saya. Harus mendefinisikan variabel lokal untuk melakukan parsing.
jxramos

Jawaban:

39

kamu tidak bisa menggunakan

double.TryParse(objReader[i].ToString(), out bd.Budget); 

ganti bd.Budget dengan beberapa variabel.

double k;
double.TryParse(objReader[i].ToString(), out k); 
dhinesh
sumber
12
mengapa menggunakan satu variabel tambahan ??
Pratik
7
@pratik Anda tidak dapat mengirimkan properti sebagai parameter keluar karena tidak ada jaminan bahwa properti tersebut benar-benar memiliki penyetel, jadi Anda memerlukan variabel tambahan.
Matt
25
@ mjd79: Alasan Anda salah. Kompilator mengetahui apakah ada penyetel atau tidak. Misalkan ada seorang setter; haruskah itu diizinkan?
Eric Lippert
21
@ dhinesh, saya pikir OP sedang mencari jawaban mengapa dia tidak bisa melakukannya, bukan hanya apa yang harus dia lakukan. Bacalah jawaban dari Hans Passant dan komentar dari Eric Lippert.
siput
2
@dhinesh Alasan "sebenarnya" mengapa dia tidak bisa melakukannya adalah karena dia menggunakan C # daripada VB yang TIDAK mengizinkan ini. Saya dari dunia VB (jelas?) Dan saya sering terkejut dengan batasan ekstra yang diterapkan C #.
SteveCinq
152

Orang lain telah memberi Anda solusinya, tetapi mengapa ini perlu: properti hanyalah gula sintaksis untuk suatu metode .

Misalnya, ketika Anda mendeklarasikan properti yang dipanggil Namedengan getter dan setter, di bawah tenda kompilator sebenarnya menghasilkan metode yang dipanggil get_Name()dan set_Name(value). Kemudian, saat Anda membaca dan menulis ke properti ini, compiler menerjemahkan operasi ini menjadi panggilan ke metode yang dihasilkan tersebut.

Saat Anda mempertimbangkan ini, menjadi jelas mengapa Anda tidak dapat meneruskan properti sebagai parameter output - Anda sebenarnya akan meneruskan referensi ke metode , bukan referensi ke obyek sebuah variabel , yang merupakan apa mengharapkan parameter output.

Kasus serupa terjadi untuk pengindeks.

Mike Chamberlain
sumber
19
Penalaran Anda benar sampai bagian terakhir. Parameter out mengharapkan referensi ke variabel , bukan ke objek .
Eric Lippert
@EricLippert tetapi bukankah variabel juga merupakan objek atau apa yang saya lewatkan?
meJustAndrew
6
@meJustAndrew: Variabel sama sekali bukan objek . Variabel adalah lokasi penyimpanan . Lokasi penyimpanan berisi (1) referensi ke objek berjenis referensi (atau null), atau (2) nilai objek berjenis nilai. Jangan bingung wadah dengan hal yang terkandung di dalamnya.
Eric Lippert
6
@meJustAndrew: Pertimbangkan sebuah objek, katakanlah, sebuah rumah. Perhatikan selembar kertas yang bertuliskan alamat rumah. Pertimbangkan laci yang berisi selembar kertas itu. Baik laci maupun kertas bukanlah rumahnya .
Eric Lippert
69

Ini adalah kasus abstraksi yang bocor. Properti sebenarnya adalah metode, get dan set aksesor untuk pengindeks dikompilasi ke metode get_Index () dan set_Index. Kompilator melakukan pekerjaan yang hebat menyembunyikan fakta itu, secara otomatis menerjemahkan sebuah tugas ke sebuah properti ke metode set_Xxx () yang sesuai misalnya.

Tapi ini akan gagal saat Anda meneruskan parameter metode dengan referensi. Itu membutuhkan kompilator JIT untuk meneruskan pointer ke lokasi memori dari argumen yang diteruskan. Masalahnya, tidak ada, menetapkan nilai properti memerlukan pemanggilan metode penyetel. Metode yang dipanggil tidak dapat membedakan antara variabel yang diteruskan vs properti yang diteruskan dan karenanya tidak dapat mengetahui apakah pemanggilan metode diperlukan.

Perlu dicatat bahwa ini benar-benar berfungsi di VB.NET. Sebagai contoh:

Class Example
    Public Property Prop As Integer

    Public Sub Test(ByRef arg As Integer)
        arg = 42
    End Sub

    Public Sub Run()
        Test(Prop)   '' No problem
    End Sub
End Class

Kompiler VB.NET memecahkan masalah ini dengan secara otomatis menghasilkan kode ini untuk metode Jalankan, yang dinyatakan dalam C #:

int temp = Prop;
Test(ref temp);
Prop = temp;

Yang merupakan solusi yang dapat Anda gunakan juga. Tidak begitu yakin mengapa tim C # tidak menggunakan pendekatan yang sama. Mungkin karena mereka tidak ingin menyembunyikan panggilan pengambil dan penyetel yang berpotensi mahal. Atau perilaku yang sama sekali tidak dapat didiagnosis yang akan Anda dapatkan ketika penyetel memiliki efek samping yang mengubah nilai properti, mereka akan menghilang setelah penetapan. Perbedaan klasik antara C # dan VB.NET, C # adalah "tidak ada kejutan", VB.NET adalah "buatlah bekerja jika Anda bisa".

Hans Passant
sumber
16
Anda benar tentang tidak ingin membuat panggilan mahal. Alasan kedua adalah bahwa semantik salin-dalam-salin memiliki semantik yang berbeda dari semantik referensi dan akan menjadi tidak konsisten untuk memiliki dua semantik yang sedikit berbeda untuk pengoperan ref. (Yang mengatakan, ada beberapa situasi langka di mana pohon ekspresi terkompilasi melakukan copy-in-copy-out, sayangnya.)
Eric Lippert
2
Apa yang benar-benar dibutuhkan adalah variasi yang lebih besar dari mode parameter-passing, sehingga compiler dapat mengganti "copy in / copy out" jika sesuai, tetapi bertengkar jika tidak.
supercat
9

Tempatkan parameter out ke dalam variabel lokal dan kemudian atur variabel menjadi bd.Budget:

double tempVar = 0.0;

if (double.TryParse(objReader[i].ToString(), out tempVar))
{
    bd.Budget = tempVar;
}

Pembaruan : Langsung dari MSDN:

Properti bukanlah variabel dan oleh karena itu tidak dapat dikirimkan sebagai parameter keluar.

Adam Houldsworth
sumber
1
@ E.vanderSpoel Untungnya saya mencabut konten tersebut, menghapus tautannya.
Adam Houldsworth
8

Mungkin menarik - Anda bisa menulis sendiri:

    //double.TryParse(, out bd.Budget);
    bool result = TryParse(s, value => bd.Budget = value);
}

public bool TryParse(string s, Action<double> setValue)
{
    double value;
    var result =  double.TryParse(s, out value);
    if (result) setValue(value);
    return result;
}
David Hollinshead
sumber
5

Ini adalah posting yang sangat lama, tetapi saya mengubah yang diterima, karena ada cara yang lebih nyaman untuk melakukan ini yang saya tidak tahu.

Ini disebut deklarasi sebaris dan mungkin selalu tersedia (seperti dalam menggunakan pernyataan) atau mungkin telah ditambahkan dengan C # 6.0 atau C # 7.0 untuk kasus seperti itu, tidak yakin, tetapi tetap berfungsi seperti pesona:

Inetad tentang ini

double temp;
double.TryParse(objReader[i].ToString(), out temp);
bd.Budget = temp;

Gunakan ini:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;
DanDan
sumber
2
Saya akan menggunakan return untuk memeriksa apakah parse berhasil jika input tidak valid.
MarcelDevG
2

Jadi Anggaran adalah properti, bukan?

Pertama-tama, setel ke variabel lokal, lalu setel nilai properti ke variabel tersebut.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;
Adriaan Stander
sumber
Terima kasih, tetapi bolehkah saya tahu mengapa demikian?
Pratik
0

Biasanya ketika saya mencoba melakukan ini, itu karena saya ingin menyetel properti saya atau membiarkannya pada nilai default. Dengan bantuan jawaban dan dynamictipe ini kita dapat dengan mudah membuat metode ekstensi string agar tetap satu baris dan sederhana.

public static dynamic ParseAny(this string text, Type type)
{
     var converter = TypeDescriptor.GetConverter(type);
     if (converter != null && converter.IsValid(text))
          return converter.ConvertFromString(text);
     else
          return Activator.CreateInstance(type);
}

Gunakan seperti itu;

bd.Budget = objReader[i].ToString().ParseAny(typeof(double));

// Examples
int intTest = "1234".ParseAny(typeof(int)); // Result: 1234
double doubleTest = "12.34".ParseAny(typeof(double)); // Result: 12.34
decimal pass = "12.34".ParseAny(typeof(decimal)); // Result: 12.34
decimal fail = "abc".ParseAny(typeof(decimal)); // Result: 0
string nullStr = null;
decimal failedNull = nullStr.ParseAny(typeof(decimal)); // Result: 0

Pilihan

Di samping catatan, jika itu adalah SQLDataReaderAnda juga dapat menggunakan GetSafeStringekstensi untuk menghindari pengecualian nol dari pembaca.

public static string GetSafeString(this SqlDataReader reader, int colIndex)
{
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

public static string GetSafeString(this SqlDataReader reader, string colName)
{
     int colIndex = reader.GetOrdinal(colName);
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

Gunakan seperti itu;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
clamchoda.dll
sumber