简体   繁体   English

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

[英]Real-world examples where C# 'out' parameters are useful?

I'm reading up on core C# programming constructs and having a hard time wrapping my head around the out parameter modifier. 我正在阅读核心C#编程结构,并且难以绕过out参数修饰符。 I know what it does by reading but am trying to think of a scenerio when I would use it. 我知道它是通过阅读做什么的,但是当我使用它的时候我正试图想出一个场景。

Can someone give me a real-world example? 有人可以给我一个真实世界的例子吗? Thanks. 谢谢。

The main motivation to using an out parameter is to allow a function to return multiple values to the caller and everyone else provided examples in the framework. 使用out参数的主要动机是允许函数将多个值返回给调用者,并且其他所有人都在框架中提供了示例。 I'll take a different approach to answering your question by exploring the reasoning behind having out parameters in the first place. 我会采取不同的方法来通过探索其背后的推理回答你的问题out在首位的参数。 I won't write out actual examples but describe them. 我不会写出实际的例子,而是描述它们。

Normally you have only one mechanism to return values, the function's return value. 通常,您只有一种机制来返回值,即函数的返回值。 Sure you could use a global (static) or instance variables too but that's not very practical nor safe to do in general (for reasons I won't explain here). 当然你也可以使用全局(静态)或实例变量,但这一般不太实际也不安全(原因我不在这里解释)。 Prior to .NET 3.5 , there wasn't a really practical way to return multiple values from a function. 在.NET 3.5之前 ,没有一种非常实用的方法可以从函数中返回多个值。 If out or ref modifiers were not available, you would have a few options: 如果outref修饰符不可用,您可以选择以下几种方法:

  • If all your values had the same type, you could return some collection of the values. 如果所有值都具有相同的类型,则可以返回一些值集合。 This is perfectly fine in most cases, you could return an array of number, list of strings, whatever. 在大多数情况下,这是完全正常的,您可以返回一个数字数组,字符串列表,等等。 This is perfect if all the values were related in exactly the same way. 如果所有值都以完全相同的方式相关,则这是完美的。 ie, All numbers were the number of items in a container, or the list was of names of guests at a party. 即,所有数字都是容器中的项目数,或者列表是聚会上的客人姓名。 But what if the values you returned represented different quantities? 但是如果您返回的值代表不同的数量呢? What if they had different types? 如果他们有不同类型怎么办? A list of objects could hold them all but it is not a very intuitive way to manipulate that sort of data. 对象列表可以包含所有对象,但这不是一种非常直观的方式来操纵那种数据。

  • For the case when you need to return multiple values of different types, the only practical option you had was to create a new class/struct type to encapsulate all these values and return an instance of that type. 对于需要返回多个不同类型值的情况,唯一可行的选择是创建一个新的类/结构类型来封装所有这些值并返回该类型的实例。 Doing so you could return strongly typed values with intuitive names and you could return multiple values this way. 这样做可以使用直观的名称返回强类型值,并且可以通过这种方式返回多个值。 The problem is that in order to get that, you had to define the type with a specific name and everything just to be able to return multiple values. 问题是,为了实现这一点,您必须使用特定名称定义类型,并且只需要能够返回多个值。 What if you wanted to return only two values which were simple enough making it impractical to create a type for it? 如果你只想返回两个足够简单的值,那么为它创建一个类型是不切实际的呢? You have a couple more options again: 你还有几个选择:

    • You could create a set of generic types to contain a fixed amount of values of varying types (like a tuple in functional languages). 您可以创建一组泛型类型,以包含固定数量的不同类型的值(如函数语言中的元组)。 But it is not as appealing to do so in a reusable manner since it wasn't part of the framework at the time. 但是,以可重复使用的方式这样做并不具有吸引力,因为它当时不是框架的一部分。 It could be put in a library but now you add a dependency on that library just for the sake of these simple types. 它可以放在库中,但现在只是为了这些简单类型而添加了对该库的依赖。 (just be glad that .NET 4.0 now includes the Tuple type) But this still doesn't solve the fact that these are simple values which means added complexity for a simple task. (只是很高兴.NET 4.0现在包含Tuple类型)但是这仍然无法解决这些简单值的事实,这意味着增加了简单任务的复杂性。

    • The option that was used was to include an out modifier which allows the caller to pass a "reference" to a variable so that the function may set the referenced variable as another way to return a value. 使用的选项是包含一个out修饰符,它允许调用者将“引用”传递给变量,以便函数可以将引用的变量设置为另一种返回值的方法。 This way of returning values was also available in C and C++ in many ways for the same reasons and played a role in influencing this decision. 由于相同的原因,这种返回值的方式在许多方面也可以在C和C ++中获得,并且在影响该决定中起到了作用。 However the difference in C# is that for an out parameter, the function must set the value to something. 但是,C#的区别在于对于out参数,函数必须将值设置为某个值。 If it doesn't, it results in a compiler error. 如果没有,则会导致编译器错误。 This makes this less error prone since by having an out parameter, you're promising the caller that you will set the value to something and they can use it, the compiler makes sure you stick to that promise. 这使得这不容易出错,因为通过out参数,您承诺调用者将值设置为某些东西并且可以使用它,编译器确保您坚持该承诺。

A note on the typical usage of the out (or ref ) modifier, it will be rare to see more than one or two out parameters. 关于out (或ref )修饰符的典型用法的注释,很少会看到多于一个或两个out参数。 In those cases, it will almost always be a much better idea to create the encapsulating type. 在这些情况下,创建封装类型几乎总是更好的主意。 You would typically use it if you needed just one more value to return. 如果您需要返回一个值,通常会使用它。

However since C#-3.0/.NET-3.5 with the introduction of anonymous types and tuples introduced in .NET 4.0, these options provided alternative methods to return multiple values of varying types easier (and more intuitive) to do. 但是,自从C#-3.0 / .NET-3.5引入.NET 4.0中引入的匿名类型和元组以来,这些选项提供了替代方法,可以更轻松地(更直观地)返回不同类型的多个值。

there are many scenarios where you would use it, but the main one would be where your method needs to return more then one parameter. 有很多场景你会使用它,但主要的是你的方法需要返回多个参数的地方。 Take, for example, the TryParse methods on int type. 例如,在int类型上使用TryParse方法。 In this case, instead of throwing an exception a bool is returned as a success/failure flag and the parsed int is return as the out param. 在这种情况下,不是抛出异常而是将bool作为成功/失败标志返回,并且解析的int作为out参数返回。 if you were to call int.Parse(...) you could potentially throw an exception. 如果你要调用int.Parse(...)你可能会抛出异常。

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

Sure, take a look at any of the TryParse methods, such as int.TryParse : 当然,看一下任何一个TryParse方法,比如int.TryParse

The idea is you actually want two pieces of information: whether a parse operation was successful (the return value), and , if so, what the result of it actually was (the out parameter). 我们的想法是你真正想要的两条信息:在分析操作是否成功(返回值), 并且 ,如果是这样,什么它的结果实际上是(在out参数)。

Usage: 用法:

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

Many developers complain of out parameters as a "code smell"; 许多开发人员抱怨out参数是“代码味道”; but they can be by far the most appropriate choice in many scenarios. 但在许多情况下,它们可能是迄今为止最合适的选择。 One very important modern example would be multithreaded code; 一个非常重要的现代例子是多线程代码; often an out parameter is necessary to permit "atomic" operations where a return value would not suffice. 通常需要out参数来允许返回值out “原子”操作。

Consider for example Monitor.TryEnter(object, ref bool) , which acquires a lock and sets a bool atomically, something that wouldn't be possible via a return value alone since the lock acquisition would necessarily happen before the return value were assigned to a bool variable. 例如Monitor.TryEnter(object, ref bool)考虑Monitor.TryEnter(object, ref bool) ,它获取一个锁以原子方式设置bool ,这是单独通过返回值无法实现的,因为锁定获取必然会在返回值分配给a之前发生。 bool变量。 (Yes, technically ref and out are not the same; but they're very close). (是的,从技术角度来说, refout不一样;但它们非常接近)。

Another good example would be some of the methods available to the collection classes in the System.Collections.Concurrent namespace new to .NET 4.0; 另一个很好的例子是.NET 4.0新增的System.Collections.Concurrent命名空间中的集合类可用的一些方法。 these provide similarly thread-safe operations such as ConcurrentQueue<T>.TryDequeue(out T) and ConcurrentDictionary<TKey, TValue>.TryRemove(TKey, out TValue) . 这些提供了类似的线程安全操作,例如ConcurrentQueue<T>.TryDequeue(out T)ConcurrentDictionary<TKey, TValue>.TryRemove(TKey, out TValue)

Output parameters are found all over the .NET framework. 输出参数遍布.NET框架。 Some of the uses I see most often are the TryParse methods, which return a boolean (indicating whether or not the parse was valid) and the actual result is returned via the output parameter. 我经常看到的一些用法是TryParse方法,它返回一个布尔值(表示解析是否有效),并通过输出参数返回实际结果。 While it's also very common place to use a class when you need to return multiple values, in such an example as this it's a little heavy handed. 虽然当你需要返回多个值时,使用类也是非常常见的地方,在这样的例子中,它有点沉重。 For more on output parameters, see Jon Skeet's article on Parameter passing in C# . 有关输出参数的更多信息,请参阅Jon Skeet关于C#中参数传递的文章。

Simple, when you have a method that returns more than one value. 很简单,当你有一个返回多个值的方法时。 One of the most "famous" cases is Dictionary.TryGetValue : 最着名的案例之一是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.");
}

As others have said - out parameters allow us to return more than one value from a method call without having to wrap the results in struct/class. 正如其他人所说 - out参数允许我们从方法调用中返回多个值,而不必将结果包装在struct / class中。

The addition of the xxx.TryParse methods greatly simplified the coding necessary to convert between a string value (frequently from the UI) and a primitive type. 添加xxx.TryParse方法大大简化了在字符串值(通常来自UI)和基本类型之间进行转换所需的编码。

An example of what you might have had to write to achieve the same functionality is here: 您可能需要编写以实现相同功能的示例如下:

/// <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;
}

The two exception checks that are avoided here make the calling code much more readable. 这里避免的两个异常检查使调用代码更具可读性。 I believe that the actual .NET implementations avoid the exceptions altogether so perform better as well. 我相信实际的.NET实现完全避免了异常,因此也表现得更好。 Similarly, this example shows how IDictionary.TryGetValue(...) makes code simpler and more efficient: 类似地,此示例显示了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);
    }
}

And all thanks to the out parameter. 并且都归功于out参数。

//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;
}
bool Int32.TryParse(String, out Int);

or something similar like Dictionary.TryGetValue. 或类似Dictionary.TryGetValue的东西。

But I would consider this one to be a not too good practice to employ it, of course, using those provided by API like the Int32 one to avoid Try-Catch is exceptions. 但是我认为使用它是一个不太好的做法,当然,使用像Int32这样的API提供的那些来避免Try-Catch是例外。

The other answers have shown how out parameters allow you to return more than one value from a method. 其他答案显示了out参数如何允许您从方法返回多个值。

I would like to describe another distinct advantage of out parameters: Improving the public interface of a class by making usage mistakes less likely . 我想描述out参数的另一个明显优势:通过减少使用错误来改进类的公共接口。

First attempt (flawed) 第一次尝试(有缺陷)

Let's consider the following interface: 让我们考虑以下界面:

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

This is the Command pattern for commands that can be undone. 这是可以撤消的命令的命令模式 However, something is wrong with this interface design: It allows you to write code that undoes a command before it has even been executed: 但是,此接口设计出现了问题:它允许您编写在执行命令之前撤消命令的代码:

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

Second attempt (still flawed) 第二次尝试(仍有缺陷)

So let's try to prevent this mistake by re-designing the interface: 因此,让我们尝试通过重新设计界面来防止这种错误:

interface IUndoableCommand
{
    IUndoableCommand Execute();
}

Now you only have access to the "undo" command once the undoable command has been executed. 现在,只有在执行了undoable命令后才能访问“undo”命令。

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

While this design is not explicitly bad, it suffers from two flaws: 虽然这种设计并没有明显不好,但它有两个缺点:

  1. It's not very logical for an Execute method to return something. Execute方法返回一些东西并不合逻辑。 If it returns something at all, you'd probably think it's some sort of success indicator (" result "), but not another "undo" command. 如果它返回一些东西,你可能会认为它是某种成功指标(“ result ”),而不是另一种“撤销”命令。

  2. It allows you to "chain" calls to Execute in the form of: 它允许您以下列形式“链接”对Execute调用:

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

    Contrary to what it looks like, someCommand will not be executed twice; 与它的外观相反, someCommand 不会被执行两次; it will effectively be cancelled. 它会被有效取消。

Final attempt with out parameter (successful) 与最后一次尝试out参数(成功)

So once again, let's try to improve the interface by replacing the return value with an out parameter: 所以再一次,让我们尝试通过用out参数替换返回值来改进接口:

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

This design suffers from none of the disadvantages shown above: 这种设计没有上述任何缺点:

  1. You cannot call the "undo" command before the actual command. 您无法在实际命令之前调用“undo”命令。
  2. Execute has no return value (or it could have a success indicator return value), as it should be. Execute没有返回值(或者它可能具有成功指示符返回值),因为它应该是。
  3. You cannot chain the Execute method of the "undo" command to the Execute method of the actual command. 你不能链Execute的“撤消”命令的方法Execute的实际命令的方法。

     IUndoableCommand undoCommand; someCommand.Execute(out undoCommand); undoCommand.Execute(… /* out someCommand */); 

(Of course once you have references to both commands, you could still do mischief, such as calling the undoCommand twice, but there's probably no way from stopping that at compile-time.) (当然,一旦你有两个命令的引用,你仍然可以做恶作剧,比如调用undoCommand两次,但是在编译时可能无法停止它。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM