Method declares a ByRef or Out parameter

ID

vbnet.maintainability.no_out_ref_params

Severity

high

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Api Design

Language

VB.NET

Description

Reports a Sub or Function that is not declared Private and that declares a parameter as ByRef, including the <Out> ByRef form used to express an output-only parameter. Three cases are not reported: Private members, since the argument is about the published surface; <DllImport> declarations, where the platform signature dictates the parameter passing; and methods whose name begins with Try, since the TryParse-style pattern - a Boolean result plus one ByRef output - is the established convention for an operation that is allowed to fail without throwing. An Overrides member is not reported either, for the same reason as <DllImport>: the signature is fixed by the base class, so the ByRef cannot be removed here without breaking the override. The cost is that a ByRef first introduced by an overridable member is only reported on the base declaration, not on the overrides that inherit it.

Rationale

A ByRef parameter makes the caller’s variable part of the method’s contract, and everything that follows from that is awkward. The call site cannot pass an expression, a property, a constant or the result of another call, so callers are forced to declare a local for the sole purpose of receiving the value; the call stops composing, and code that would read as one expression becomes three statements. It also makes the order of effects observable: the method may write the parameter before it fails, so a caller has to know whether a thrown exception leaves the variable updated, half-updated or untouched, and nothing in the signature answers that. For a Function, a ByRef parameter means the operation has two results with no stated relationship - which one is authoritative when they disagree, and is the ByRef value even meaningful when the return value indicates failure - and that question lands on every caller. ByRef is also the harder shape for consumers outside the language: dynamic callers, most interop layers and any binding over the API have to model an in-out slot rather than a value. A method that returns what it computed, or a small type that carries the several values together, has none of these properties and is understandable from the signature alone.

The following code illustrates the pattern detected by this rule:

Public Class Calculator

    ' FLAGGED: Method declares a ByRef or Out parameter
    Public Sub Swap(ByRef first As Decimal, ByRef second As Decimal)
        Dim temp = first
        first = second
        second = temp
    End Sub

Remediation

Return the value instead of writing it through a parameter. Where an operation produces several values, return a type that holds them together - a small Structure, a class, or a tuple - and name its members for what they mean. Where the operation may fail, either throw, or use the Try pattern deliberately: name the method TryX, return Boolean, and leave the single ByRef output as its only out-parameter.

' Before: the caller must declare a local, and cannot pass an expression
Public Function Apply(amount As Decimal, ByRef remainder As Decimal) As Decimal

' After
Public Structure SplitResult
    Public ReadOnly Whole As Decimal
    Public ReadOnly Remainder As Decimal
End Structure

Public Function Apply(amount As Decimal) As SplitResult

Configuration

This detector does not need any configuration.