属性或索引器可能无法作为out或ref参数传递


86

我收到上述错误,无法解决。我用谷歌搜索了一下,但不能摆脱它。

场景:

我有类BudgetAllocate,其属性是双精度类型的budget。

在我的dataAccessLayer中,

在我的一堂课中,我试图做到这一点:

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

哪个抛出此错误:

在编译时,属性或索引器可能不会作为out或ref参数传递。

我什至尝试了这个:

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

其他一切工作正常,并且存在层之间的引用。


在bd.Budget中,bd是BudgetAllocate类的对象。对不起我忘记了。
Pratik 2010年




刚发现这种方法与用户类型一起使用,该用户类型具有已定义的字段,我希望DataGrid从字段中开始填充字段,然后才开始学习它仅具有属性。切换到属性会破坏我在字段上使用的一些ref参数。要定义局部变量进行解析。
jxramos

Answers:


37

你不能使用

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

用一些变量替换bd.Budget。

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

11
为什么要使用一个额外的变量?
Pratik 2010年

6
@pratik您不能将属性作为out参数传入,因为无法保证该属性实际上有一个setter,因此您需要额外的变量。
马特2010年

23
@ mjd79:您的推理不正确。编译器知道是否有一个setter。假设有一个塞特犬。应该允许吗?
埃里克·利珀特

21
@dhinesh,我认为OP正在寻找一个答案,说明为什么他不能做到这一点,而不仅仅是他必须要做的事情。阅读Hans Passant的回答和Eric Lippert的评论。
slugster 2010年

2
@dhinesh之所以不能做到这一点的“真正”原因是因为他使用的是C#而不是允许这样做的VB。我来自VB世界(很明显吗?),我常常对C#施加的额外限制感到惊讶。
SteveCinq '12

149

其他人为您提供了解决方案,但是要说明为什么这样做是必要的:属性只是方法的语法糖。

例如,当您声明一个Name带有getter和setter的属性时,编译器实际上会生成称为get_Name()和的方法set_Name(value)。然后,当您读取和写入此属性时,编译器会将这些操作转换为对这些生成的方法的调用。

考虑到这一点,很明显为什么不能将属性作为输出参数传递-您实际上是传递对方法的引用,而不是对变量的对象的引用,这是输出参数所期望的。

索引器也存在类似情况。


19
您的推理是正确的,直到最后一刻。out参数期望引用变量,而不是对象
埃里克·利珀特

@EricLippert但变量也不是对象还是我缺少什么?
meJustAndrew

6
@meJustAndrew:变量绝对不是对象。变量是存储位置。存储位置包含(1)对引用类型的对象的引用(或null),或(2)值类型的对象的值。不要混淆容器中包含的东西。
埃里克·利珀特

6
@meJustAndrew:考虑一个对象,例如一个房子。考虑一张写有房子地址的纸。考虑一个包含那张纸的抽屉。 抽屉和纸都不是房子
埃里克·利珀特

69

这是泄漏抽象的情况。属性实际上是一个方法,索引器的获取设置访问器将被编译为get_Index()和set_Index方法。编译器在隐藏该事实方面做得非常出色,例如,它会自动将对属性的赋值转换为相应的set_Xxx()方法。

但是,当您通过引用传递方法参数时,这会变得很麻烦。这就要求JIT编译器将一个指针传递给传递的参数的内存位置。问题是,没有一个问题,分配属性的值需要调用setter方法。被调用的方法无法分辨出传递的变量与传递的属性之间的区别,因此无法知道是否需要方法调用。

值得注意的是,这实际上在VB.NET中有效。例如:

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

VB.NET编译器通过自动为Run方法生成以下代码(以C#表示)来解决此问题:

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

您也可以使用哪种解决方法。不太清楚为什么C#团队不使用相同的方法。可能是因为他们不想隐藏潜在的昂贵的getter和setter调用。或者,当设置器具有更改属性值的副作用时,您将获得完全无法诊断的行为,这些副作用将在分配后消失。C#和VB.NET之间的经典区别是C#是“毫无意外”,VB.NET是“如果可以的话,使它正常工作”。


15
您是正确的,不想产生昂贵的电话。第二个原因是,复制中复制语义与引用语义具有不同的语义,并且为引用传递具有两种稍有不同的语义将是不一致的。(这就是说,不幸的是,在某些罕见的情况下,编译的表达式树确实会进行复制中复制输出。)
Eric Lippert 2010年

2
真正需要的是更多种类的参数传递模式,以便编译器可以在适当的地方替换“ copy in / copy out”,但在不需要的情况下则发出嘎嘎声。
supercat

9

将out参数放入局部变量,然后将变量设置为bd.Budget

double tempVar = 0.0;

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

更新:直接从MSDN:

属性不是变量,因此不能作为输出参数传递。


1
@ E.vanderSpoel幸运的是,我提出了内容,删除了链接。
亚当·霍尔兹沃思

8

可能感兴趣-您可以编写自己的:

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

5

这是一篇非常古老的文章,但是我正在修改已被接受的文章,因为这样做的方式更加简便,我不知道。

它称为内联声明,可能一直可用(如在使用语句中一样),或者在某些情况下可能已经与C#6.0或C#7.0一起添加了(不确定),但无论如何都像一种魅力:

这个的

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

用这个:

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

2
如果输入无效,我将使用return检查解析是否成功。
MarcelDevG

1

因此,预算是财产,对吗?

而是首先将其设置为局部变量,然后将属性值设置为该变量。

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

谢谢。但是我能知道为什么吗?
Pratik 2010年

0

通常,当我尝试执行此操作时,这是因为我想设置属性或将其保留为默认值。借助这个答案dynamic类型,我们可以轻松地创建一个字符串扩展方法,以使它保持一行简单。

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

像这样使用;

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

可选的

附带一提,如果是这样,SQLDataReader您也可以使用GetSafeString扩展名,以避免读者出现null异常。

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

像这样使用;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.