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之前 ,没有一种非常实用的方法可以从函数中返回多个值。 如果out
或ref
修饰符不可用,您可以选择以下几种方法:
-
如果您的所有值都具有相同的类型,则可以返回一些值的集合。 在大多数情况下,这是完全正常的,你可以返回一个数字数组,字符串列表,等等。 如果所有值都以完全相同的方式相关,则这是完美的。 即,所有数字都是容器中的项目数,或者列表是一方的客人姓名。 但是如果您返回的值代表不同的数量呢? 如果他们有不同类型怎么办? 对象列表可以包含所有对象,但它不是一种非常直观的方式来操纵那种数据。
-
对于需要返回不同类型的多个值的情况,唯一可行的选择是创建一个新的类/结构类型来封装所有这些值并返回该类型的实例。 这样做可以使用直观的名称返回强类型值,并且可以通过这种方式返回多个值。 问题是,为了实现这一点,您必须使用特定名称定义类型,并且所有内容都只是为了能够返回多个值。 如果你只想返回两个足够简单的值,那么为它创建一个类型是不切实际的呢? 你还有几个选择:
-
您可以创建一组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
变量。 (是的,从技术角度来说, ref
和out
不一样;但它们非常接近)。
另一个很好的例子是.NET 4.0新增的System.Collections.Concurrent
命名空间中的集合类可用的一些方法。 这些提供了类似的线程安全操作,例如ConcurrentQueue
和ConcurrentDictionary
。
输出参数遍布.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...
虽然这种设计并没有明显不好,但它有两个缺点:
-
Execute
方法返回一些东西并不合逻辑。 如果它返回一些东西,你可能会认为它是某种成功指标(“result
”),而不是另一种“撤销”命令。 -
它允许您以下列forms“链接”对
Execute
调用:someCommand.Execute().Execute(); // ^^^^^^^^^^^^^^^^^^^^ // guess what this does!?
与它看起来相反,
someCommand
不会被执行两次; 它会被有效取消。
带参数的最终尝试(成功)
所以再一次,让我们尝试通过用out
参数替换返回值来改进接口:
interface IUndoableCommand { void Execute(out IUndoableCommand undoCommand); }
这种设计没有上述任何缺点:
- 您无法在实际命令之前调用“undo”命令。
-
Execute
没有返回值(或者它可能有一个成功指示符返回值),因为它应该是。 -
您不能将“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; }