【问题标题】:A property or indexer may not be passed as an out or ref parameter属性或索引器不能作为 out 或 ref 参数传递
【发布时间】:2011-05-29 23:18:03
【问题描述】:

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

场景:

我有类BudgetAllocate,其属性为budget,属于double 类型。

在我的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);

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

【问题讨论】:

标签: c# .net error-handling


【解决方案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 answerdynamic 类型的帮助下,我们可以轻松地创建一个字符串扩展方法来保持它的简洁性。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-23
      • 2020-11-26
      • 2011-06-16
      • 2012-09-08
      • 2012-07-30
      • 2011-09-23
      相关资源
      最近更新 更多