一个属性或索引器不能作为out或ref参数传递。

119

我遇到了上述错误并无法解决它。

情境:

我有一个名为BudgetAllocate的类,其属性budgetdouble类型。

在我的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
1
可能是 通过泛型类型参数访问属性 的重复问题。 - Chris Moschini
刚刚发现在使用用户类型时,其中定义了一些字段,我希望从中填充DataGrid,但后来才知道它只能自动执行属性。将其转换为属性破坏了我在字段上使用的一些ref参数。我必须定义局部变量来进行解析。 - jxramos
9个回答

188

其他人已经给出了解决方案,但为什么需要这样做呢:属性只是一个方法的语法糖。

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

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

索引器也存在类似情况。


10
@meJustAndrew说:“变量绝对不是对象”。变量是一个“存储位置”。存储位置“包含”(1)指向引用类型对象的引用(或 null),或者(2)值类型对象的值。不要混淆容器和其中所包含的东西。 - Eric Lippert
9
@meJustAndrew:想象一个物体,比如一栋房子。想象一个纸片上写着这栋房子的地址。再想象一个抽屉里面放着这张纸片。这个抽屉和纸片都不是这栋房子 - Eric Lippert
@MichaelS:你的评论中有许多不准确之处。例如:C#将变量定义为存储位置,变量既不是与该位置关联的名称(如果有的话),也不是存储在该位置的数据。它是位置。在C#中,对象不仅仅是“变量的集合”。等等。 - Eric Lippert
@MichaelS:作为标准化C#语言的委员会成员,我熟悉规范的位置,但还是谢谢你提供的链接。我认为也许我应该是我所想象的类比元素的仲裁者,但请随意提出你喜欢的新颖类比,这样我们就可以共同学习。 - Eric Lippert
@EricLippert:你的比喻很清晰,但并不真正代表编程。我的比喻非常标准,并且准确地反映了计算机的实际工作方式(除了我最初省略了指令数据)。由于我没有看到你回复的原始评论,我会假设你的评论在上下文中更有意义,就此打住。 - MichaelS

77

这是一个漏洞抽象的例子。属性实际上是一个方法,用于索引器的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调用,或者是因为当setter具有更改属性值的副作用时会出现无法诊断的行为,在赋值后这些副作用将消失。这是C#和VB.NET之间的经典区别,C#是“没有惊喜”,VB.NET是“如果能使其工作就行”。


16
你对不想产生昂贵的电话是正确的。第二个原因是,复制-粘贴语义与引用语义有不同的语义,因此在ref传递中拥有两个微妙不同的语义是不一致的。(尽管如此,在某些罕见情况下编译表达式树确实会进行复制-粘贴操作。) - Eric Lippert
2
真正需要的是更多种参数传递模式,这样编译器就可以在适当的情况下替换“复制输入/复制输出”,但在不适当的情况下则会发出警告。 - supercat
这个变通方法在使用Interlocked.Add时不起作用。突然之间它就不再是原子操作了。 - undefined

44
你不能使用
double.TryParse(objReader[i].ToString(), out bd.Budget); 

将bd.Budget替换为某个变量。
double k;
double.TryParse(objReader[i].ToString(), out k); 
bd.Budget = k;

14
为什么要使用额外的变量? - Pratik
8
@pratik 你不能将属性作为输出参数传递,因为无法保证该属性实际上具有setter,所以你需要额外的变量。 - matt
28
你的推论是错误的。编译器知道是否有setter方法。假设存在setter方法,它应该被允许吗? - Eric Lippert
21
@dhinesh,我认为问题的提问者正在寻求一个关于为什么他不能这样做的答案,而不仅仅是他应该做什么。请阅读Hans Passant的答案和Eric Lippert的评论。 - slugster
2
@dhinesh 他不能这样做的“真正”原因是因为他使用的是C#而不是允许这样做的VB。我来自VB世界(显然?),我经常对C#强加的额外限制感到惊讶。 - SteveCinq
显示剩余2条评论

13

可能会感兴趣 - 你可以自己编写:

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

10

将输出参数放入本地变量中,然后将该变量设置为bd.Budget

double tempVar = 0.0;

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

更新: 直接引用MSDN的原话:

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


1
@E.vanderSpoel 幸运的是,我将内容提取出来,并删除了链接。 - Adam Houldsworth

8

这是一篇很老的文章,但我要修改已经被接受的答案,因为有一种更方便的方法可以做到这一点,而我之前并不知道。

它被称为内联声明,可能一直都可用(如使用语句),或者已经在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;

3
如果输入无效,我会使用返回值来检查解析是否成功。 - MarcelDevG

2

我之前也遇到了同样的问题(5分钟前),我使用旧式属性和getter和setter解决了它,这些属性使用变量。

public List<int> bigField = new List<int>();
public List<int> BigField { get { return bigField; } set { bigField = value; } }

所以,我只是使用了bigField变量。我不是程序员,如果我误解了问题,真的很抱歉。


2

所以Budget是一个属性,对吗?

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

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

谢谢。但是我可以知道为什么吗? - Pratik

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扩展来避免从reader中得到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));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接