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

我正在阅读核心C#编程结构,并且难以绕过out参数修饰符。 我知道通过阅读它做了什么,但我想在使用它时想到一个场景。

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

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

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

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

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

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

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

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

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

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

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

当然,看一下任何一个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 (eg, 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参数是“代码味道”; 但在许多情况下,它们可能是迄今为止最合适的选择。 一个非常重要的现代例子是multithreading代码; 通常需要out参数来允许返回值out “primefaces”操作。

考虑一下Monitor.TryEnter(object, ref bool) ,它获取一个锁以primefaces方式设置bool ,这是因为锁定获取必然发生在将返回值分配给a之前的单独返回值。 bool变量。 (是的,从技术角度来说, refout不一样;但它们非常接近)。

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

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

很简单,当你有一个返回多个值的方法时。 最着名的案例之一是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."); } 

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

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

您可能需要编写以实现相同function的示例如下:

 ///  /// Example code for how  might be implemented. ///  /// A string to convert to an integer. /// The result of the parse if the operation was successful. /// true if the  parameter was successfully /// parsed into the  integer; false otherwise. 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; } 

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

 private readonly IDictionary mDictionary = new Dictionary(); 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参数。

 bool Int32.TryParse(String, out Int); 

或类似Dictionary.TryGetValue的东西。

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

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

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

第一次尝试(有缺陷)

让我们考虑以下界面:

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

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

 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. 它允许您以下列forms“链接”对Execute调用:

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

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

带参数的最终尝试(成功)

所以再一次,让我们尝试通过用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 */); 

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

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