C# 4.0 необязательные аргументы out/ref

Разрешает ли C# 4.0 опционально out или же ref аргументы?

11 ответов

Решение

Как уже упоминалось, это просто не разрешено, и я думаю, что это имеет очень хороший смысл. Однако, чтобы добавить некоторые подробности, вот цитата из спецификации C# 4.0, раздел 21.1:

Формальные параметры конструкторов, методов, индексаторов и типов делегатов могут быть объявлены необязательными:

с фиксированным параметром:
атрибуты opt параметр-модификатор opt идентификатор типа default-аргумент opt
по умолчанию аргументы:
= выражение

  • Фиксированный параметр с аргументом по умолчанию является необязательным параметром, тогда как фиксированный параметр без аргумента по умолчанию является обязательным параметром.
  • Обязательный параметр не может появиться после необязательного параметра в списке формальных параметров.
  • ref или же out Параметр не может иметь аргумент по умолчанию.

Нет.

Обходной путь - перегрузить другим методом, который не имеет параметров out / ref и который просто вызывает ваш текущий метод.

public bool SomeMethod(out string input)
{
    ...
}

// new overload
public bool SomeMethod()
{
    string temp;
    return SomeMethod(out temp);
}

Обновление: если у вас есть C# 7.0, вы можете упростить:

// new overload
public bool SomeMethod()
{
    return SomeMethod(out _);    // declare out as an inline discard variable
}

(Спасибо @Oskar / @Reiner за это.)

Нет, но другой замечательной альтернативой является использование метода общего шаблонного класса для необязательных параметров следующим образом:

public class OptionalOut<Type>
{
    public Type Result { get; set; }
}

Тогда вы можете использовать его следующим образом:

public string foo(string value, OptionalOut<int> outResult = null)
{
    // .. do something

    if (outResult != null) {
        outResult.Result = 100;
    }

    return value;
}

public void bar ()
{
    string str = "bar";

    string result;
    OptionalOut<int> optional = new OptionalOut<int> ();

    // example: call without the optional out parameter
    result = foo (str);
    Console.WriteLine ("Output was {0} with no optional value used", result);

    // example: call it with optional parameter
    result = foo (str, optional);
    Console.WriteLine ("Output was {0} with optional value of {1}", result, optional.Result);

    // example: call it with named optional parameter
    foo (str, outResult: optional);
    Console.WriteLine ("Output was {0} with optional value of {1}", result, optional.Result);
}

На самом деле есть способ сделать это, что разрешено в C#. Это возвращается к C++ и скорее нарушает красивую объектно-ориентированную структуру C#.

Используйте этот метод с осторожностью!

Вот как вы объявляете и пишете свою функцию с необязательным параметром:

unsafe public void OptionalOutParameter(int* pOutParam = null)
{
    int lInteger = 5;
    // If the parameter is NULL, the caller doesn't care about this value.
    if (pOutParam != null) 
    { 
        // If it isn't null, the caller has provided the address of an integer.
        *pOutParam = lInteger; // Dereference the pointer and assign the return value.
    }
}

Затем вызовите функцию следующим образом:

unsafe { OptionalOutParameter(); } // does nothing
int MyInteger = 0;
unsafe { OptionalOutParameter(&MyInteger); } // pass in the address of MyInteger.

Чтобы это скомпилировать, вам нужно включить небезопасный код в опциях проекта. Это действительно хакерское решение, которое обычно не следует использовать, но если вам нужно какое-то странное, загадочное, таинственное, вдохновленное руководством решение, ДЕЙСТВИТЕЛЬНО нужен дополнительный параметр out в C#, тогда это позволит вам сделать именно это.

ICYMI: Включенный в перечисленные здесь новые функции для C# 7.0, "Discards" теперь разрешен как параметры out в форме _, чтобы позволить вам игнорировать параметры, которые вас не интересуют:

p.GetCoordinates(out var x, out _); // I only care about x

PS, если вы также перепутали с частью "out var x", прочитайте новую ссылку о "Out Variables" по ссылке.

Нет, но вы можете использовать делегата (например, Action) как альтернатива.

Вдохновленный ответом Робина Р., когда я столкнулся с ситуацией, когда я думал, что мне нужен необязательный параметр out, я вместо этого использовал Action делегировать. Я позаимствовал его пример кода, чтобы изменить его для использования Action<int> чтобы показать различия и сходства:

public string foo(string value, Action<int> outResult = null)
{
    // .. do something

    outResult?.Invoke(100);

    return value;
}

public void bar ()
{
    string str = "bar";

    string result;
    int optional = 0;

    // example: call without the optional out parameter
    result = foo (str);
    Console.WriteLine ("Output was {0} with no optional value used", result);

    // example: call it with optional parameter
    result = foo (str, x => optional = x);
    Console.WriteLine ("Output was {0} with optional value of {1}", result, optional);

    // example: call it with named optional parameter
    foo (str, outResult: x => optional = x);
    Console.WriteLine ("Output was {0} with optional value of {1}", result, optional);
}

Преимущество этого заключается в том, что необязательная переменная появляется в исходном тексте как обычное int (компилятор оборачивает его в класс замыкания, а не в явном виде в класс, определенный пользователем).

Переменная требует явной инициализации, потому что компилятор не может предположить, что Action будет вызван до завершения вызова функции.

Он не подходит для всех случаев использования, но хорошо работал для моего реального случая использования (функция, которая предоставляет данные для модульного теста, и где новому модульному тесту необходим доступ к некоторому внутреннему состоянию, отсутствующему в возвращаемом значении).

Для простых типов вы можете сделать это, используя небезопасный код, хотя это не идиоматично и не рекомендуется. Вот так:

      // unsafe since remainder can point anywhere
// and we can do arbitrary pointer manipulation
public unsafe int Divide( int x, int y, int* remainder = null ) {
    if( null != remainder ) *remainder = x % y;
    return x / y;
}

Тем не менее, нет никакой теоретической причины, по которой C# не мог бы в конечном итоге разрешить что-то вроде вышеприведенного с безопасным кодом, например, как показано ниже:

      // safe because remainder must point to a valid int or to nothing
// and we cannot do arbitrary pointer manipulation
public int Divide( int x, int y, out? int remainder = null ) {
    if( null != remainder ) *remainder = x % y;
    return x / y;
}

Хотя все может стать интереснее:

      // remainder is an optional output parameter
// (to a nullable reference type)
public int Divide( int x, int y, out? object? remainder = null ) {
    if( null != remainder ) *remainder = 0 != y ? x % y : null;
    return x / y;
}

На прямой вопрос был дан ответ в других хорошо проголосованных ответах, но иногда стоит рассмотреть другие подходы, основанные на том, чего вы пытаетесь достичь.

Если вам нужен необязательный параметр, чтобы позволить вызывающей стороне запрашивать дополнительные данные из вашего метода, на которых основывается какое-либо решение, альтернативный дизайн состоит в том, чтобы переместить эту логику принятия решения в ваш метод и разрешить вызывающей стороне дополнительно передавать значение для этого критерии принятия решения. Например, вот метод, который определяет точку компаса вектора, в котором мы можем захотеть передать обратно величину вектора, чтобы вызывающий мог потенциально решить, должен ли быть достигнут какой-то минимальный порог до того, как компас -точечное суждение находится достаточно далеко от начала координат и поэтому однозначно справедливо:

      public enum Quadrant {
    North,
    East,
    South,
    West
}

// INVALID CODE WITH MADE-UP USAGE PATTERN OF "OPTIONAL" OUT PARAMETER
public Quadrant GetJoystickQuadrant([optional] out magnitude)
{
    Vector2 pos = GetJoystickPositionXY();
    float azimuth = Mathf.Atan2(pos.y, pos.x) * 180.0f / Mathf.PI;
    Quadrant q;
    if (azimuth > -45.0f && azimuth <= 45.0f) q = Quadrant.East;
    else if (azimuth > 45.0f && azimuth <= 135.0f) q = Quadrant.North;
    else if (azimuth > -135.0f && azimuth <= -45.0f) q = Quadrant.South;
    else q = Quadrant.West;
    if ([optonal.isPresent(magnitude)]) magnitude = pos.Length();
    return q;
}

В этом случае мы могли бы переместить эту логику «минимальной величины» в метод и получить гораздо более чистую реализацию, особенно потому, что вычисление величины включает в себя квадратный корень, поэтому вычислительно неэффективно, если все, что мы хотим сделать, это сравнение величин. , так как мы можем сделать это с квадратными значениями:

      public enum Quadrant {
    None, // Too close to origin to judge.
    North,
    East,
    South,
    West
}

public Quadrant GetJoystickQuadrant(float minimumMagnitude = 0.33f)
{
    Vector2 pos = GetJoystickPosition();
    if (minimumMagnitude > 0.0f && pos.LengthSquared() < minimumMagnitude * minimumMagnitude)
    {
        return Quadrant.None;
    }
    float azimuth = Mathf.Atan2(pos.y, pos.x) * 180.0f / Mathf.PI;
    if (azimuth > -45.0f && azimuth <= 45.0f) return Quadrant.East;
    else if (azimuth > 45.0f && azimuth <= 135.0f) return Quadrant.North;
    else if (azimuth > -135.0f && azimuth <= -45.0f) return Quadrant.South;
    return Quadrant.West;
}

Конечно, это не всегда может быть жизнеспособным. Поскольку в других ответах упоминается С# 7.0, если вместо этого вы действительно возвращаете два значения и позволяете вызывающей стороне необязательно игнорировать одно, идиоматический С# будет возвращать кортеж из двух значений и использовать кортежи С# 7.0 с позиционным инициализаторы и параметр _ "discard":

      public (Quadrant, float) GetJoystickQuadrantAndMagnitude()
{
    Vector2 pos = GetJoystickPositionXY();
    float azimuth = Mathf.Atan2(pos.y, pos.x) * 180.0f / Mathf.PI;
    Quadrant q;
    if (azimuth > -45.0f && azimuth <= 45.0f) q = Quadrant.East;
    else if (azimuth > 45.0f && azimuth <= 135.0f) q = Quadrant.North;
    else if (azimuth > -135.0f && azimuth <= -45.0f) q = Quadrant.South;
    else q = Quadrant.West;
    return (q, pos.Length());
}

(Quadrant q, _) = GetJoystickQuadrantAndMagnitude();
if (q == Quadrant.South)
{
    // Do something.
}

Используйте перегруженный метод без параметра out, чтобы вызвать метод с параметром out для C# 6.0 и ниже. Я не уверен, почему C# 7.0 для.NET Core даже является правильным ответом для этой темы, когда его специально спросили, может ли C# 4.0 иметь необязательный параметр out. Ответ - нет!

Как насчет этого?

public bool OptionalOutParamMethod([Optional] ref string pOutParam)
{
    return true;
}

Вы все еще должны передать значение параметру из C#, но это необязательный параметр ref.

void foo(ref int? n)
{
    return null;
}
Другие вопросы по тегам