C#'out'参数有用的真实示例?

时间:2010-11-05 01:30:47

标签: c# out-parameters

我正在阅读核心C#编程结构,并且难以绕过out参数修饰符。我知道它是通过阅读来做什么的,但是当我使用它的时候我想要想象一下。

有人可以给我一个真实世界的例子吗?感谢。

9 个答案:

答案 0 :(得分:18)

使用out参数的主要动机是允许函数将多个值返回给调用者,并且其他所有人都在框架中提供了示例。我将采用不同的方法来回答您的问题,首先探讨具有out参数的原因。我不会写出实际的例子,而是描述它们。

通常,您只有一种机制可以返回值,即函数的返回值。当然你也可以使用全局(静态)或实例变量,但这一般不太实际也不安全(原因我不在这里解释)。 在.NET 3.5 之前,没有一种非常实用的方法可以从函数中返回多个值。如果outref修饰符不可用,您可以选择以下几种方法:

  • 如果您的所有值都具有相同的类型,则可以返回一些值集合。在大多数情况下,这是完全正常的,您可以返回一个数字数组,字符串列表,等等。如果所有值都以完全相同的方式相关,则这是完美的。即,所有数字都是容器中的物品数量,或者列表是一方的客人姓名。但是如果您返回的值代表不同的数量呢?如果他们有不同类型怎么办?对象列表可以包含所有对象,但这不是一种非常直观的方式来操纵那种数据。

  • 对于需要返回多个不同类型值的情况,唯一可行的选择是创建一个新的类/结构类型来封装所有这些值并返回该类型的实例。这样做可以使用直观的名称返回强类型值,并且可以通过这种方式返回多个值。问题是,为了实现这一点,您必须使用特定名称定义类型,并且只需要能够返回多个值。如果你只想返回两个足够简单的值,那么为它创建一个类型是不切实际的呢?你还有几个选择:

    • 您可以创建一组泛型类型,以包含固定数量的不同类型的值(如函数式语言中的元组)。但是,以可重复使用的方式这样做并不具有吸引力,因为它当时不是框架的一部分。它可以放在库中,但现在只是为了这些简单类型而添加了对该库的依赖。 (只是很高兴.NET 4.0现在包含Tuple类型)但这仍然无法解决这些简单值的事实,这意味着增加了简单任务的复杂性。

    • 使用的选项是包含一个out修饰符,它允许调用者将“引用”传递给变量,以便函数可以将引用的变量设置为另一种返回值的方法。由于相同的原因,这种返回值的方式在许多方面也可以在C和C ++中获得,并且在影响该决定中起到了作用。但是,C#的区别在于out参数,函数必须将值设置为某个值。如果没有,则会导致编译器错误。这使得这不容易出错,因为通过使用out参数,您承诺调用者将值设置为某些东西并且可以使用它,编译器会确保您坚持这个承诺。

关于out(或ref)修饰符的典型用法的注释,很少会看到多于一个或两个out参数。在这些情况下,创建封装类型几乎总是更好的主意。如果你需要一个值,你通常会使用它。

然而,自从C#-3.0 / .NET-3.5引入了.NET 4.0中引入的匿名类型和元组以来,这些选项提供了替代方法,可以更容易(更直观)地返回不同类型的多个值。

答案 1 :(得分:17)

在很多场景中你会使用它,但主要的是你的方法需要返回多个参数的地方。例如,TryParse类型上的int方法。在这种情况下,不是抛出异常而是将bool作为成功/失败标志返回,并且解析的int作为out参数返回。如果你打电话给int.Parse(...),你可能会抛出异常。

string str = "123456";
int val;
if ( !int.TryParse(str,out val) )
{
// do some error handling, notify user, etc.
}

答案 2 :(得分:6)

当然,请查看任何TryParse方法,例如int.TryParse

这个想法是你实际上想要两个信息:解析操作是否成功(返回值),,如果是,它的结果是什么实际上是(out参数)。

用法:

string input = Console.ReadLine();
int value;

// First we check the return value, which is a bool
// indicating success or failure.
if (int.TryParse(input, out value))
{
    // On success, we also use the value that was parsed.
    Console.WriteLine(
        "You entered the number {0}, which is {1}.",
        value,
        value % 2 == 0 ? "even" : "odd"
    );
}
else
{
    // Generally, on failure, the value of an out parameter
    // will simply be the default value for the parameter's
    // type (e.g., default(int) == 0). In this scenario you
    // aren't expected to use it.
    Console.WriteLine(
        "You entered '{0}', which is not a valid integer.",
        input
    );
}

许多开发人员抱怨out参数是“代码味道”;但在许多情况下,它们可能是迄今为止最合适的选择。一个非常重要的现代例子是多线程代码;通常需要out参数来允许返回值不足的“原子”操作。

考虑例如Monitor.TryEnter(object, ref bool),它获取一个锁以原子方式设置bool,这是因为锁定获取将无法通过单独的返回值实现的必须在将返回值分配给bool变量之前发生。 (是的,技术上refout不一样;但它们非常接近)。

另一个很好的例子是.NET 4.0新增的System.Collections.Concurrent命名空间中的集合类可用的一些方法;这些提供了类似的线程安全操作,例如ConcurrentQueue<T>.TryDequeue(out T)ConcurrentDictionary<TKey, TValue>.TryRemove(TKey, out TValue)

答案 3 :(得分:4)

输出参数遍布.NET框架。我最常见的一些用法是TryParse方法,它返回一个布尔值(表示解析是否有效),并通过输出参数返回实际结果。虽然当你需要返回多个值时,使用类也是非常常见的地方,在这样的例子中,它有点沉重。有关输出参数的更多信息,请参阅Jon Skeet关于Parameter passing in C#的文章。

答案 4 :(得分:2)

简单,当你有一个返回多个值的方法时。 其中一个最“着名”的案例是Dictionary.TryGetValue

string value = "";

if (openWith.TryGetValue("tif", out value))
{
    Console.WriteLine("For key = \"tif\", value = {0}.", value);
}
else
{
    Console.WriteLine("Key = \"tif\" is not found.");
}

答案 5 :(得分:1)

正如其他人所说的那样 - out参数允许我们从方法调用中返回多个值,而不必将结果包装在struct / class中。

添加xxx.TryParse方法大大简化了在字符串值(通常来自UI)和基本类型之间进行转换所需的编码。

您可能必须编写以实现相同功能的示例如下:

/// <summary>
/// Example code for how <see cref="int.TryParse(string,out int)"/> might be implemented.
/// </summary>
/// <param name="integerString">A string to convert to an integer.</param>
/// <param name="result">The result of the parse if the operation was successful.</param>
/// <returns>true if the <paramref name="integerString"/> parameter was successfully 
/// parsed into the <paramref name="result"/> integer; false otherwise.</returns>
public bool TryParse(string integerString, out int result)
{
    try
    {
        result = int.Parse(integerString);
        return true;
    }
    catch (OverflowException)
    {
        // Handle a number that was correctly formatted but 
        // too large to fit into an Int32.
    }
    catch (FormatException)
    {
        // Handle a number that was incorrectly formatted 
        // and so could not be converted to an Int32.
    }

    result = 0; // Default.
    return false;
}

这里避免的两个异常检查使调用代码更具可读性。我相信实际的.NET实现完全避免了异常,因此也表现得更好。同样,这个例子展示了IDictionary.TryGetValue(...)如何使代码更简单,更高效:

private readonly IDictionary<string,int> mDictionary = new Dictionary<string, int>();

public void IncrementCounter(string counterKey)
{
    if(mDictionary.ContainsKey(counterKey))
    {
        int existingCount = mDictionary[counterKey];

        mDictionary[counterKey] = existingCount + 1;
    }
    else
    {
        mDictionary.Add(counterKey, 1);
    }
}

public void TryIncrementCounter(string counterKey)
{
    int existingCount;
    if (mDictionary.TryGetValue(counterKey, out existingCount))
    {
        mDictionary[counterKey] = existingCount + 1;
    }
    else
    {
        mDictionary.Add(counterKey, 1);
    }
}

所有这都归功于out参数。

答案 6 :(得分:0)

bool Int32.TryParse(String, out Int);

或类似Dictionary.TryGetValue。

但是我认为这个使用它不是一个好习惯,当然,使用像Int32那样的API提供的那些来避免Try-Catch是例外。

答案 7 :(得分:0)

其他答案显示out参数如何允许您从方法返回多个值。

我想描述out参数的另一个明显优势:通过减少使用错误来改善类的公共接口。

第一次尝试(有缺陷)

让我们考虑以下界面:

interface IUndoableCommand
{
    void Execute();
    IUndoableCommand GetUndoCommand();
}

这是 Command pattern表示可以撤消的命令。但是,这个界面设计出了问题:它允许你编写在执行命令之前撤消命令的代码:

someCommand.GetUndoCommand().Execute();
someCommand.Execute();
// ^ this is obviously wrong, but will compile.

第二次尝试(仍有缺陷)

因此,让我们尝试通过重新设计界面来防止这种错误:

interface IUndoableCommand
{
    IUndoableCommand Execute();
}

现在,只有在执行了undoable命令后才能访问“undo”命令。

var undoCommand = someCommand.Execute();
undoCommand.Execute();
// ^ this is better, but has other problems...

虽然这种设计并没有明显不好,但它有两个缺点:

  1. Execute方法返回某些内容并不合逻辑。如果它返回某些东西,你可能会认为它是某种成功指标(“result”),而不是另一种“撤销”命令。

  2. 它允许您以{/ p>的形式“链接”对Execute的调用

    someCommand.Execute().Execute();
    //         ^^^^^^^^^^^^^^^^^^^^
    //        guess what this does!?
    

    与它看起来相反,someCommand执行两次;它将被有效取消。

  3. out参数的最终尝试(成功)

    再一次,让我们尝试通过用out参数替换返回值来改进界面:

    interface IUndoableCommand
    {
        void Execute(out IUndoableCommand undoCommand);
    }
    

    这种设计没有上述任何缺点:

    1. 您无法在实际命令之前调用“undo”命令。
    2. Execute没有返回值(或者它可能有一个成功指标返回值),因为它应该是。
    3. 您无法将“undo”命令的Execute方法链接到实际命令的Execute方法。

      IUndoableCommand undoCommand;
      someCommand.Execute(out undoCommand);
      undoCommand.Execute(… /* out someCommand */);
      
    4. (当然,一旦你有两个命令的引用,你仍然可以做恶作剧,比如两次调用undoCommand,但是在编译时可能无法停止它。)

答案 8 :(得分:0)

//out key word is used in function instead of return. we can use multiple parameters by using out key word
public void outKeyword(out string Firstname, out string SecondName)
{
    Firstname = "Muhammad";
    SecondName = "Ismail";

}
//on button click Event
protected void btnOutKeyword_Click(object sender, EventArgs e)
{
    string first, second;
    outKeyword(out first, out second);
    lblOutKeyword.Text = first + "  " + second;
}