属性或索引器不能作为 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】:

这是一个泄漏抽象的例子。属性实际上是一种方法,索引器的 getset 访问器被编译为 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 参数传递的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将属性作为“out”或“ref”参数传递?

是否可以将属性作为“out”或“ref”参数传递?

为啥迭代器方法不能采用“ref”或“out”参数?

c#面试问题及答案

不能在 lambda 表达式中使用 ref 或 out 参数

ref和out实际用法