属性或索引器不能作为 out 或 ref 参数传递
Posted
技术标签:
【中文标题】属性或索引器不能作为 out 或 ref 参数传递【英文标题】:A property or indexer may not be passed as an out or ref parameter 【发布时间】:2011-05-29 23:18:03 【问题描述】:我收到上述错误并且无法解决。 我用谷歌搜索了一下,但无法摆脱它。
场景:
我有类BudgetAllocate
,其属性为budget
,属于double
类型。
在我的dataAccessLayer
,
在我的一门课程中,我正在尝试这样做:
double.TryParse(objReader[i].ToString(), out bd.Budget);
抛出此错误的原因:
编译时间。
我什至试过这个:
double.TryParse(objReader[i].ToString().Equals(DBNull.Value) ? "" : objReader[i].ToString(), out bd.Budget);
其他一切正常,层之间的引用存在。
【问题讨论】:
在 bd.Budget 中,bd 是 BudgetAllocate 类的对象。对不起,我忘记了。 C# property and ref parameter, why no sugar?的可能重复 Accessing properties through Generic type parameter的可能重复 Passing properties by reference in C#的可能重复 刚刚发现这与一个定义了字段的用户类型一起工作,我预计会填充一个DataGrid
,然后开始学习它只自动使用属性。切换到属性会破坏我在字段中使用的一些参考参数。必须定义局部变量来进行解析。
【参考方案1】:
其他人已经给了你解决方案,但至于为什么这是必要的:属性只是方法的语法糖。
例如,当您使用 getter 和 setter 声明名为 Name
的属性时,编译器实际上会生成名为 get_Name()
和 set_Name(value)
的方法。然后,当您读取和写入此属性时,编译器会将这些操作转换为对这些生成的方法的调用。
当您考虑这一点时,为什么不能将属性作为输出参数传递就很明显了——您实际上是传递对 方法 的引用,而不是对 的引用一个对象一个变量,这是一个输出参数所期望的。
索引器也存在类似情况。
【讨论】:
你的推理直到最后一点都是正确的。 out 参数期望引用 variable,而不是 object。 @EricLippert 但变量不是对象还是我缺少什么? @meJustAndrew:变量绝对不是对象。变量是存储位置。存储位置包含 (1) 对引用类型(或 null)对象的引用,或 (2) 值类型对象的值。不要把容器和里面的东西搞混了。 @meJustAndrew:考虑一个物体,比如房子。考虑一张纸,上面写着房子的地址。考虑一个包含那张纸的抽屉。 抽屉和纸都不是房子。【参考方案2】:这是一个泄漏抽象的例子。属性实际上是一种方法,索引器的 get 和 set 访问器被编译为 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 是“如果可以的话,让它工作”。
【讨论】:
你不想产生昂贵的电话是正确的。第二个原因是,copy-in-copy-out 语义与引用语义具有不同的语义,并且对于 ref 传递有两个微妙不同的语义是不一致的。 (也就是说,不幸的是,编译后的表达式树在极少数情况下会进行复制输入复制输出。) 真正需要的是更多种类的参数传递模式,以便编译器可以在适当的情况下替换“复制输入/复制输出”,但在不合适的情况下会发出声音。【参考方案3】:你不能使用
double.TryParse(objReader[i].ToString(), out bd.Budget);
用一些变量替换 bd.Budget。
double k;
double.TryParse(objReader[i].ToString(), out k);
【讨论】:
为什么要使用一个额外的变量?? @pratik 您不能将属性作为 out 参数传入,因为无法保证该属性实际上具有 setter,因此您需要额外的变量。 @mjd79:你的推理不正确。编译器知道是否有setter。假设有一个二传手;应该允许吗? @dhinesh,我认为 OP 正在寻找为什么他不能这样做的答案,而不仅仅是他必须做的事情。阅读 Hans Passant 的答案和 Eric Lippert 的 cmets。 @dhinesh 他不能这样做的“真正”原因是因为他使用的是 C# 而不是允许这样做的 VB。我来自 VB 世界(显然?),我经常对 C# 施加的额外限制感到惊讶。【参考方案4】:将out参数放入一个局部变量中,然后将该变量设置为bd.Budget
:
double tempVar = 0.0;
if (double.TryParse(objReader[i].ToString(), out tempVar))
bd.Budget = tempVar;
更新:直接来自 MSDN:
属性不是变量,并且 因此不能被传递出去 参数。
【讨论】:
@E.vanderSpoel 幸运的是我把内容拿出来,删除了链接。【参考方案5】:可能感兴趣 - 您可以自己编写:
//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;
【讨论】:
【参考方案6】:这是一个非常古老的帖子,但我正在修改已接受的内容,因为有一种更方便的方法可以做到这一点,我不知道。
它被称为内联声明并且可能一直可用(如在 using 语句中),或者在这种情况下可能已经添加了 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;
【讨论】:
如果输入无效,我会使用返回来检查解析是否成功。【参考方案7】:所以预算是一个属性,对吗?
而是先将其设置为局部变量,然后将属性值设置为该变量。
double t = 0;
double.TryParse(objReader[i].ToString(), out t);
bd.Budget = t;
【讨论】:
谢谢。但我可以知道为什么吗?【参考方案8】:通常当我尝试这样做时,是因为我想设置我的属性或将其保留为默认值。在this answer 和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
扩展名来避免来自读者的空异常。
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));
【讨论】:
以上是关于属性或索引器不能作为 out 或 ref 参数传递的主要内容,如果未能解决你的问题,请参考以下文章