你正确的使用Out/Ref了吗?
2010-12-03 19:19 空逸云 阅读(721) 评论(6) 编辑 收藏 举报最近在努力的学习MVC.并逐步的把Asp.net MVC应用到实际项目中去.先前.找了不少开源项目,心里一个高兴啊.一边摸索一边借鉴(抄)着别人的代码,并希望能从中吸收大牛们的思想.在CodePlex找到一个不错的开源项目.借鉴着就着手开发了.一路上风平浪静.抄代码抄得起劲.不料出现了一个让我措手不及的错误.该代码实现如下.
/// <summary> /// 获取配置文件中DappSettings节点下指定索引键的Int类型的的值 /// </summary> /// <param name="key">索引键</param> /// <param name="defaultValue">默认值</param> /// <returns>Int</returns> private static int getInt32(string key, int? defaultValue) { return getValue<int>(key, (v, pv) => int.TryParse(v, out pv), defaultValue); }
private static T getValue<T>(string key, Func<string, T, bool> parseValue, T? defaultValue) where T : struct { string value = appSettings[key]; if (value != null) { T parsedValue = default(T); if (parseValue(value, parsedValue)) return parsedValue; else throw new ApplicationException(string.Format("Settings '{0}' was not a valid {1}", key, typeof(T).FullName)); } if (!defaultValue.HasValue) throw new ApplicationException("在配置文件的appSettings节点结合中找不到key为" + key + "的子节点,且没有指定默认值"); else return defaultValue.Value; }
该方法就是获取配置文件里相关的AppSetting项,并将其转换成Int32值.很不错的实现.但是实际上运行的时候却发现返回的值永远就是0,这不对啊.于是仔细的分析了这两个方法.主要就是利用了out关键字.
我们知道,利用Out/Ref关键字,能使值类型的参数达到引用类型的参数的效果.
瓦解黑盒子
仔细分析了这两个方法.将核心实现重现如下
public class MyClass { static int parseValue(string str, int pv) { if (ValidParse(str, pv)) return pv; else return 0; } static bool ValidParse(string str, int input) { return Int32.TryParse(str, out input); } public static void Main() { string str = "100"; int num = 0; Console.WriteLine(parseValue(str, num)); } }
看得到.调用步骤如下Main->parseValue->ValidParse,Main得到了一个字符串变量,想把它转换成Int类型,于是找到了parseValue方法.parseValue方法要求Main提供字符串和一个Int变量,它的做法是利用ValidParse方法,尝试能否吧str转换成Int,如果可以的话就返回Int,注意!此处的原意和TryParse相识.而ValidParse方法就是简单的调用了TryParse.大体看下来.这个流程是没什么问题的.而且应该得到正确的答案.但事实却并非如此.图解一下
可能图画得不太精确.但能诠释这个过程就好了.调用的过程.
第一步.看到.str是引用过去了.而num并不是原值,而是做了一个副本拷贝.这样.即使在parseValue中num的值发生改变.也只是副本数据发生了改变.
第二步.可以看到.str还是引用,即保持第一手数据.而num又做了一次副本拷贝.在validParse上调用了TryParse方法.此时,ValidParse上的num改变了.但是注意.此处也是副本修改.所以.
第三步(返回),num数据丢失.这也就解释了为什么我们的结果总会是0了.
既然知道了原因.那就好办了.我们尝试着加上out
public class MyClass { static int parseValue(string str, int pv) { if (ValidParse(str, out pv)) return pv; else return 0; } static bool ValidParse(string str, out int input) { return Int32.TryParse(str, out input); } public static void Main() { string str = "100"; int num = 0; Console.WriteLine(parseValue(str, num)); } }
改动并不大.只是在第二步中调用加入out,但是恰是这个out.让我们的结果正确了.原因就是第三步使用的num并不是步骤二的副本.而是步骤二的数据.所以它修改了原数据,没发生数据丢失.
优化代码
精简代码一向是我们所追崇的.特别是.Net3.5之后的Lambda.不用的话岂不浪费.废话不多说.我们开工
delegate bool ParseFunc<T, S>(T T1, out S S1);
先声明一个委托,注意,这里的委托中用到了out.
private static T getOutValue<T>(string key, ParseFunc<string,T> parseValue, T? defaultValue) where T : struct { string value = appSettings[key]; if (value != null) { T parsedValue = default(T); if (parseValue(value, out parsedValue)) return parsedValue; else throw new ApplicationException(string.Format("Settings '{0}' was not a valid {1}", key, typeof(T).FullName)); } if (!defaultValue.HasValue) throw new ApplicationException("在配置文件的appSettings节点结合中找不到key为" + key + "的子节点,且没有指定默认值"); else return defaultValue.Value; }
再改造一下GetValue方法.此处必须注意的是,在传参时必须使用Out,当然.你没用out,编译器也不会让你通过.最后
public static int getInt32(string key, int? defaultValue) { return getOutValue<int>(key, (string v, out int pv) => int.TryParse(v, out pv), defaultValue); }
大功告成.这里主要是理解Lambda表达式的应用.相信你对Lambda的应用有了一个不一样的理解了吧!
PS:可能这个标题和文章内容有些格格不入,甚至有标题党的感觉.没什么文采.大家就体谅体谅.
出处:http://kongyiyun.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。