Vb.net 是';TryAction()';方法邪恶?

Vb.net 是';TryAction()';方法邪恶?,vb.net,pass-by-reference,fxcop,Vb.net,Pass By Reference,Fxcop,我正试图在我的代码中提高FxCop的遵从性(这是有史以来第一次),但我有点被道德问题所困扰。我有一个方法GetText(),它从远程服务器返回字符串,但在某些情况下可以抛出异常。这就是为什么我还有一个方法TryGetText(ByRef text As String),它返回一个布尔值,指示调用是否成功。如果为true,则将返回值指定给文本变量 我认为这种结构是完全可以接受的,因为即使是微软也这样做(例如Integer.TryParse)。 FxCop却在啧啧啧啧地叫我,口述“你不能通过参考!”

我正试图在我的代码中提高FxCop的遵从性(这是有史以来第一次),但我有点被道德问题所困扰。我有一个方法
GetText()
,它从远程服务器返回字符串,但在某些情况下可以抛出异常。这就是为什么我还有一个方法
TryGetText(ByRef text As String)
,它返回一个布尔值,指示调用是否成功。如果为true,则将返回值指定给文本变量

我认为这种结构是完全可以接受的,因为即使是微软也这样做(例如
Integer.TryParse
)。 FxCop却在啧啧啧啧地叫我,口述“你不能通过参考!”

为了避免这个警告(有很多警告),我用StringBuilder替换了参数。但是,尽管现在是兼容的,我不认为它真的以任何方式改进了我的代码

之前:

    Public Function TryGetText(ByRef text As String) As Boolean
        Dim command As New GetTextCommand(Me)
        Dim result As CommandResult = ProcessCommand(command, True)
        If result.CommandStatus <> Constants.Status.Failed Then
            text = result.Text
            Return True
        Else
            Return False
        End If
    End Function
公共函数TryGetText(ByRef文本作为字符串)作为布尔值
Dim命令作为新的GetTextCommand(Me)
Dim结果为CommandResult=ProcessCommand(命令,真)
如果result.CommandStatus Constants.Status.Failed,则
text=结果。text
返回真值
其他的
返回错误
如果结束
端函数
之后:

    Public Function TryGetText(builder As Text.StringBuilder) As Boolean
        Dim command As New GetTextCommand(Me)
        Dim result As CommandResult = ProcessCommand(command, True)
        If result.CommandStatus <> Constants.Status.Failed Then
            builder.Clear()
            builder.Length = result.Text.Length
            builder.Append(result.Text)
            Return True
        Else
            Return False
        End If
    End Function
公共函数TryGetText(builder作为Text.StringBuilder)作为布尔值
Dim命令作为新的GetTextCommand(Me)
Dim结果为CommandResult=ProcessCommand(命令,真)
如果result.CommandStatus Constants.Status.Failed,则
builder.Clear()
builder.Length=result.Text.Length
builder.Append(result.Text)
返回真值
其他的
返回错误
如果结束
端函数

这是可以接受的ByRef使用,还是应该使用stringbuilder替代方案?对于我使用此构造的每个方法,我都不太愿意抑制此警告。我也不觉得stringbuilder变体提高了代码的可用性。

这可能是对
ByRef
的一个很好的使用,因此我倾向于为这种情况添加一个例外。如果在Visual Studio中使用代码分析功能,只需将以下属性添加到该方法:

<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")> _
Public Function TryGetText(ByRef text As String) As Boolean
_
公共函数TryGetText(ByRef文本作为字符串)作为布尔值

嗯,我想每个人都讨厌太多的byrefs。两个已经太多了。虽然在您的案例中,有许多方法,并且它们都遵循相同的模式,但这听起来并不是一个真正的问题。 您可以选择不坚持Microsoft的Try*方法模式

如果成功,为什么不返回字符串而不是布尔值;如果失败,为什么不返回Nothing/空? 然后可以使用String.IsNullOrEmpty(resultText)测试TryGetText输出


实际上,它更多的是代码,但它确实解决了警告问题(如果这是您真正想要的)。

在创建线程安全类之类的东西时,TryAction方法非常有用。它将测试和操作这两个步骤结合到一个原子操作中。Microsoft在并发集合类中大量使用它。比如说


因此,我认为“你不应通过引用”这一笼统的说法在所有情况下都不应被视为福音。有合法的用途。在您的特定情况下,ByRef似乎没有StringBuilder版本那么笨拙。但是如果一个简单的
string GetText()
在当前返回false的情况下返回null/Nothing是等价的,那么这似乎是最好的

当您尝试以函数式方式使用方法时,通过引用传递会使事情变得复杂。您可以考虑使用可空类型或元组或您自己的选项类型。

由于我对VB不太熟悉,这里有一个C#示例:

struct选项{
public bool containseElement{get;private set;}
私有T元素;
公共T元素{
得到{
如果(!containseElement)抛出新的NoElementException();
返回元素;
}
设置{
元素=值;
containseElement=true;
}
}
public T GetElementOrDefault(T defaultValue){
返回containseElement?元素:defaultValue;
}
}
选项GetText(){
...
}

在这种情况下,我不能让它只返回null,因为null对于我正在做的事情来说是一个有效的值。所以我想这证实了ByRef的正确用法。这些规则是好仆人,但不是好主人。我本来可以这样做的,但null(或空)(甚至空格)都是可能的返回值。只有当服务器拒绝返回文本时,调用才会失败。我的代码需要知道它是否失败,或者服务器是否返回空字符串。那么最好使用byref和SuppressMessageAttribute。或者你可以把你的响应包装成一些东西,除了告诉你实际的字符串结果外,还可以告诉你操作状态,这会使事情复杂化?我可能应该调查一下。我想我以前从未使用过元组…是的,这是一种这样做的方法,并且可以节省一个自定义类。最简单的元组就像字典中的KeyValuePair。它们最多有8个可能的条目,但我认为您只需要一个元组。更多信息:这是很棒的东西!我想知道为什么微软从来不在基类库中使用元组。只要用XML注释正确地标记返回值,这看起来就简单多了。或者您知道如何将元组属性(Item1,Item2,…)重命名为更具描述性的属性吗?(编辑:我刚刚意识到这是一个多么愚蠢的问题。)你能在C#中生成可为空的字符串吗?我检查了一下以确保它不允许我进入VB(尽管字符串在其他方面的行为类似于值类型)。不,你不能,因为字符串已经是一个对象了。但是,您可以直接返回null,正如上面所建议的那样。
struct Option<T> {
   public bool ContainsElement { get; private set; }
   private T element;
   public T Element {
      get {
         if (!ContainsElement) throw new NoElementException ();
         return element;
      }
      set {
         element = value;
         ContainsElement = true;
      }
   }
   public T GetElementOrDefault (T defaultValue) {
      return ContainsElement ? element : defaultValue;
   }
}

Option<string> GetText () {
   ...
}