检查空值的正确方法是啥?
Posted
技术标签:
【中文标题】检查空值的正确方法是啥?【英文标题】:What is the proper way to check for null values?检查空值的正确方法是什么? 【发布时间】:2012-04-05 00:26:24 【问题描述】:我喜欢 null-coalescing 运算符,因为它可以很容易地为可空类型分配默认值。
int y = x ?? -1;
这很好,除非我需要用x
做一些简单的事情。例如,如果我想检查Session
,那么我通常最终不得不写一些更冗长的东西。
我希望我能做到:
string y = Session["key"].ToString() ?? "none";
但是你不能因为.ToString()
在空检查之前被调用所以如果Session["key"]
为空则它会失败。我最终这样做了:
string y = Session["key"] == null ? "none" : Session["key"].ToString();
在我看来,它比三行替代方案更有效并且更好:
string y = "none";
if (Session["key"] != null)
y = Session["key"].ToString();
即使这样可行,我仍然很好奇是否有更好的方法。似乎无论如何我总是要引用Session["key"]
两次;一次用于检查,再次用于分配。有什么想法吗?
【问题讨论】:
这是我希望 C# 有一个像 Groovy has 这样的“安全导航运算符”(.?
)的时候。
@Cameron:这是我希望 C# 可以将可空类型(包括引用类型)视为 monad 的时候,因此您不需要“安全导航运算符”。
空引用的发明者称其为“十亿美元的错误”,我倾向于同意。见infoq.com/presentations/…
他的实际错误是可空和非空标签类型的不安全(非语言强制)混合。
【参考方案1】:
这是我的小型类型安全“Elvis 运算符”,适用于不支持 ? 的 .NET 版本。
public class IsNull
public static O Substitute<I,O>(I obj, Func<I,O> fn, O nullValue=default(O))
if (obj == null)
return nullValue;
else
return fn(obj);
第一个参数是被测试的对象。二是功能。第三是空值。所以对于你的情况:
IsNull.Substitute(Session["key"],s=>s.ToString(),"none");
它对于可空类型也非常有用。例如:
decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
【讨论】:
【参考方案2】:您也可以使用as
,如果转换失败,则生成null
:
Session["key"] as string ?? "none"
即使有人在Session["key"]
中填入int
,这也会返回"none"
。
【讨论】:
这仅在您首先不需要ToString()
时才有效。
我很惊讶还没有人反对这个答案。这在语义上与 OP 想要做的完全不同。
@Timwi:OP 使用ToString()
将包含字符串的对象转换为字符串。您可以对obj as string
或(string)obj
执行相同的操作。这是 ASP.NET 中相当常见的情况。
@Andomar:不,OP 在一个他没有提到类型的对象(即Session["key"]
)上调用ToString()
。它可以是任何类型的对象,不一定是字符串。【参考方案3】:
我们使用一种叫做NullOr
的方法。
用法
// Call ToString() if it’s not null, otherwise return null
var str = myObj.NullOr(obj => obj.ToString());
// Supply default value for when it’s null
var str = myObj.NullOr(obj => obj.ToString()) ?? "none";
// Works with nullable return values, too —
// this is properly typed as “int?” (nullable int)
// even if “Count” is just int
var count = myCollection.NullOr(coll => coll.Count);
// Works with nullable input types, too
int? unsure = 47;
var sure = unsure.NullOr(i => i.ToString());
来源
/// <summary>Provides a function delegate that accepts only value types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOrTInput,TResult(TInput,FuncStructTInput,TResult)"/>
/// work without ***ing with <see cref="ObjectExtensions.NullOrTInput,TResult(TInput,FuncClassTInput,TResult)"/>.</remarks>
public delegate TResult FuncStruct<in TInput, TResult>(TInput input) where TResult : struct;
/// <summary>Provides a function delegate that accepts only reference types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOrTInput,TResult(TInput,FuncClassTInput,TResult)"/>
/// work without ***ing with <see cref="ObjectExtensions.NullOrTInput,TResult(TInput,FuncStructTInput,TResult)"/>.</remarks>
public delegate TResult FuncClass<in TInput, TResult>(TInput input) where TResult : class;
/// <summary>Provides extension methods that apply to all types.</summary>
public static class ObjectExtensions
/// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
/// <typeparam name="TInput">Type of the input value.</typeparam>
/// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
/// <param name="input">Input value to check for null.</param>
/// <param name="lambda">Function to apply the input value to if it is not null.</param>
public static TResult NullOr<TInput, TResult>(this TInput input, FuncClass<TInput, TResult> lambda) where TResult : class
return input == null ? null : lambda(input);
/// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
/// <typeparam name="TInput">Type of the input value.</typeparam>
/// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
/// <param name="input">Input value to check for null.</param>
/// <param name="lambda">Function to apply the input value to if it is not null.</param>
public static TResult? NullOr<TInput, TResult>(this TInput input, Func<TInput, TResult?> lambda) where TResult : struct
return input == null ? null : lambda(input);
/// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
/// <typeparam name="TInput">Type of the input value.</typeparam>
/// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
/// <param name="input">Input value to check for null.</param>
/// <param name="lambda">Function to apply the input value to if it is not null.</param>
public static TResult? NullOr<TInput, TResult>(this TInput input, FuncStruct<TInput, TResult> lambda) where TResult : struct
return input == null ? null : lambda(input).Nullable();
【讨论】:
是的,这是对预期问题的更通用答案 - 你打败了我 - 并且是安全导航的候选者(如果你不介意 lambda-s 用于简单的事情) - 但是写起来还是有点麻烦,好吧:)。就我个人而言,我总是选择 ? : 取而代之(如果不贵的话,无论如何都要重新排列)... ...而“命名”是这个问题的真正问题 - 似乎没有什么真正描述正确(或“添加”太多),或者很长 - NullOr 很好,但过于强调在“空”IMO(加上你还有??仍然)-“财产”或“安全”是我使用的。 value.Dot(o=>o.property) ?? @default 也许? @NSGaga:我们在名称上来回讨论了很长一段时间。我们确实考虑过Dot
,但发现它太难以描述了。我们选择NullOr
作为自我解释和简洁之间的一个很好的权衡。如果你真的根本不在乎命名,你可以一直叫它_
。如果你觉得 lambda 写起来太麻烦,你可以使用 sn-p,但我个人觉得这很容易。至于? :
,你不能用更复杂的表达式,你必须把它们移到一个新的本地; NullOr
可以避免这种情况。【参考方案4】:
如果它始终是string
,您可以转换:
string y = (string)Session["key"] ?? "none";
如果有人将int
或Session["key"]
中的内容塞入其中,这样做的好处是可以抱怨而不是隐藏错误。 ;)
【讨论】:
【参考方案5】:所有建议的解决方案都很好,并回答了问题;所以这只是稍微扩展一下。目前大多数答案只处理空验证和字符串类型。您可以扩展 StateBag
对象以包含通用 GetValueOrDefault
方法,类似于 Jon Skeet 发布的答案。
一个简单的通用扩展方法,它接受一个字符串作为键,然后对会话对象进行类型检查。如果对象为null或者不是同一类型,则返回默认值,否则返回会话值强类型。
类似的东西
/// <summary>
/// Gets a value from the current session, if the type is correct and present
/// </summary>
/// <param name="key">The session key</param>
/// <param name="defaultValue">The default value</param>
/// <returns>Returns a strongly typed session object, or default value</returns>
public static T GetValueOrDefault<T>(this HttpSessionState source, string key, T defaultValue)
// check if the session object exists, and is of the correct type
object value = source[key]
if (value == null || !(value is T))
return defaultValue;
// return the session object
return (T)value;
【讨论】:
您能否提供此扩展方法的使用示例? StateBag 不是处理视图状态而不是会话吗?我正在使用 ASP.NET MVC 3,所以我真的无法简单地访问视图状态。我想你想扩展HttpSessionState
。
这个答案需要检索值 3x 和 2 如果成功则强制转换。 (我知道这是一本字典,但初学者可能会在昂贵的方法上使用类似的做法。)
T value = source[key] as T; return value ?? defaultValue;
@jberger 无法使用“as”转换为值,因为泛型类型没有类约束,因为您可能希望返回诸如 bool 之类的值。 @AlexFord 抱歉,您可能希望延长会议的 HttpSessionState
。 :)
确实如此。正如理查德所指出的,需要约束。 (......如果你想使用值类型,还有另一种方法)【参考方案6】:
我的偏好是一次性使用安全强制转换为字符串,以防与密钥一起存储的对象不是一个。使用ToString()
可能不会得到您想要的结果。
var y = Session["key"] as string ?? "none";
正如@Jon Skeet 所说,如果您发现自己经常使用扩展方法,或者更好,但可能是与强类型 SessionWrapper 类结合使用的扩展方法。即使没有扩展方法,强类型包装器也可能是个好主意。
public class SessionWrapper
private HttpSessionBase Session get; set;
public SessionWrapper( HttpSessionBase session )
Session = session;
public SessionWrapper() : this( HttpContext.Current.Session )
public string Key
get return Session["key"] as string ?? "none";
public int MaxAllowed
get return Session["maxAllowed"] as int? ?? 10
用作
var session = new SessionWrapper(Session);
string key = session.Key;
int maxAllowed = session.maxAllowed;
【讨论】:
【参考方案7】:如果你经常这样做特别是ToString()
,那么你可以编写一个扩展方法:
public static string NullPreservingToString(this object input)
return input == null ? null : input.ToString();
...
string y = Session["key"].NullPreservingToString() ?? "none";
当然也可以是采用默认值的方法:
public static string ToStringOrDefault(this object input, string defaultValue)
return input == null ? defaultValue : input.ToString();
...
string y = Session["key"].ToStringOrDefault("none");
【讨论】:
StackExchange 的 DataExplorer 具有与此类似的扩展方法,并具有多个默认值的额外好处。string IsNullOrEmptyReturn(this string s, params string[] otherPossibleResults)
code.google.com/p/stack-exchange-data-explorer/source/browse/…
我完全不能同意这一点。 object
上的扩展方法是诅咒和垃圾代码库,并且在 null this
值上运行而不会出错的扩展方法是纯粹的邪恶。
@NickLarsen:我说一切都要适度。与 null 一起使用的扩展方法可能非常有用,IMO - 只要它们清楚了解它们的作用。【参考方案8】:
Skeet 的回答是最好的——尤其是我认为他的ToStringOrNull()
非常优雅,最适合您的需要。我想在扩展方法列表中再添加一个选项:
为null返回原始对象或默认字符串值:
// Method:
public static object OrNullAsString(this object input, string defaultValue)
if (defaultValue == null)
throw new ArgumentNullException("defaultValue");
return input == null ? defaultValue : input;
// Example:
var y = Session["key"].OrNullAsString("defaultValue");
使用var
作为返回值,因为它将作为原始输入的类型返回,仅在null
时作为默认字符串
【讨论】:
如果不需要null
defaultValue
,为什么要抛出异常(即input != null
)?
input != null
eval 将返回对象本身。 input == null
返回作为参数提供的字符串。因此,一个人可以调用.OnNullAsString(null)
- 但目的(尽管很少有用的扩展方法)是确保您要么取回对象,要么取回默认字符串......永远不会为空
input!=null
场景只有在defaultValue!=null
也成立时才会返回输入;否则它会抛出一个ArgumentNullException
。【参考方案9】:
创建辅助函数
public static String GetValue( string key, string default )
if ( Session[ key ] == null ) return default;
return Session[ key ].toString();
string y = GetValue( 'key', 'none' );
【讨论】:
【参考方案10】:怎么样
string y = (Session["key"] ?? "none").ToString();
【讨论】:
@Matthew: 不,因为 Session 值是 Object 类型的 @BlackBear 但返回的值很可能是一个字符串,所以强制转换是有效的 这是对我的问题最直接的答案,所以我要标记答案,但 Jon Skeet 的扩展方法.ToStringOrDefault()
是我首选的方法。但是,我在 Jon 的扩展方法中使用了这个答案 ;)
我不喜欢这个,因为如果你在会话中塞入了任何其他类型的对象,而不是你期望的,你可能会在你的程序中隐藏一些微妙的错误。我宁愿使用安全演员表,因为我认为它可能会更快地显示错误。它还避免了对字符串对象调用 ToString()。
@tvanfosson:我不确定我是否理解。你能举个例子吗?以上是关于检查空值的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
SqlDataReader 检查空值的最佳方法 -sqlDataReader.IsDBNull vs DBNull.Value