02.net开发实用:c#默认值和可空类型常见的操作
>说明常见值类型默认值简单类型枚举、结构类型、可以为null的值类型如何有效判断统一判断默认值反射中应用第一步:直接根据key,给对应的属性赋值第二步:先手动转换为属性对应的类型再进行赋值第三步:在Convert.ChangeType 处理前,手动判断是否为null第四步:判断属性是否为可空类型,并对可空类型进行转换处理
说明
.net core 3.0
本文整理了一些关于c#默认值和可空类型常见的操作
常见值类型默认值
值类型包括简单类型、枚举类型、结构类型、可以为null的值类型
简单类型
- 有符号的整型:sbyte、short、int、long。默认值为 0
- 无符号的整型:byte、ushort、uint、ulong。默认值为 0
- Unicode 字符:char。默认值为 0'\0'
- IEEE 二进制浮点:float、double。默认值为 0
- 高精度十进制浮点数:decimal。默认值为 0
- 布尔:bool。默认值为 false(0)
x [Test] public void TestDefaualt() { // 有符号的整型 sbyte sbyteValue = default; short shortValue = default; int intValue = default; long longValue = default; // 无符号的整型 byte byteValue = default; ushort ushortValue = default; uint uintValue = default; ulong ulongValue = default; // Unicode 字符 char charValue = default; // IEEE 二进制浮点 float floatValue = default; double doubleValue = default; // 高精度十进制浮点数 decimal decimalValue = default; // 布尔 bool boolValue = default;
Console.WriteLine($"sbyteValue={sbyteValue};shortValue={shortValue};intValue={intValue};longValue={longValue};"); // sbyteValue=0;shortValue=0;intValue=0;longValue=0; Console.WriteLine($"byteValue={byteValue};ushortValue={ushortValue};uintValue={uintValue};ulongValue={ulongValue};"); // byteValue=0;ushortValue=0;uintValue=0;ulongValue=0; Console.WriteLine($"charValue={charValue};"); // charValue=\u0000; Console.WriteLine($"floatValue={floatValue};doubleValue={doubleValue};"); // floatValue=0;doubleValue=0; Console.WriteLine($"decimalValue={decimalValue};boolValue={boolValue};"); // decimalValue=0;boolValue=False; }枚举、结构类型、可以为null的值类型
- 枚举类型默认值会取值为0的枚举项
- 结构类型的默认值指的是该结构内部字段的默认值,会将所有值类型的字段设置为其默认值,将所有引用类型的字段设置为 null
- 可空的值类型,默认值为null。
- 注意:引用类型默认值都是null。string属于引用类型,所以string的默认值也是null
xxxxxxxxxx [Test] public void TestEnumDefaualt() { // 枚举类型的default,默认会取值为0的枚举项 CheckOptionEnum enumValue = default; CatStruct catStruct = default; // 结构类型default相当于无参构造,结构类型的无参构造会将所有值类型的字段设置为其默认值,将所有引用类型的字段设置为 null DogStruct dogStruct = default; System.IO.FileInfo fileInfo = default; DateTime timeValue = default; // 0001/1/1... Guid guidValue = default; // 000...-...000
// 可以为 null 的值类型 int? intNullValue = default; long? longNullValue = default; bool? boolNullValue = default;
Console.WriteLine($"enumValue={enumValue};"); // enumValue=No; Console.WriteLine($"catStruct={catStruct};"); // catStruct=Test20231110.Test.Tests+CatStruct; Console.WriteLine($"fileInfo={fileInfo};"); // fileInfo=; Console.WriteLine($"dogStruct={dogStruct};"); // dogStruct=0号,名字:; Console.WriteLine($"timeValue={timeValue};guidValue={guidValue};"); // timeValue=0001/1/1 0:00:00;guidValue=00000000-0000-0000-0000-000000000000; // 可空的值类型,默认值为null Console.WriteLine($"intNullValue={intNullValue};longNullValue={longNullValue};boolNullValue={boolNullValue}"); // intNullValue=;longNullValue=;boolNullValue= }如何有效判断
各种类型,如何有效判断?
- 对于引用类型,我么可以使用
x != null判断是否为默认值 - 对于值类型,我们可以使用
x != default判断是否为默认值。若该变量的值刚好等于该类型的默认值,则该变量为默认值 - 对于可空的值类型,我们还可以使用
x.HasValue判断是否有值,而且 HasValue 不会引发空指针异常
xxxxxxxxxx [Test] public void TestCheckIsDefault() { System.IO.FileInfo fileInfo = default; int intValue = 0; DateTime timeValue = default; DateTime nowValue = DateTime.Now; DateTime? nullTimeValue = default; // 完整:default(DateTime?)
DateTime? nullTimeValue2 = default(DateTime); // 此处赋值的是DateTime的默认值 int? intValue2 = null;
Console.WriteLine($"fileInfo 为默认值? {fileInfo == null};"); // True
Console.WriteLine($"timeValue 为默认值? {timeValue == default};"); // True Console.WriteLine($"nowValue 为默认值? {nowValue == default};"); // False Console.WriteLine($"intValue 为默认值? {intValue == default};"); // True // 可空的值类型,默认值为null Console.WriteLine($"nullTimeValue 为默认值? {nullTimeValue == default};nullTimeValue 为null?{nullTimeValue == null}"); // True;True Console.WriteLine($"nullTimeValue2 为默认值?{nullTimeValue2 == default};nullTimeValue2 为null?{nullTimeValue2 == null}"); // False;False
// 对于可空的值类型,我们还可以使用HasValue来进行判断,而且 HasValue 不会引发空指针异常 Console.WriteLine($"nullTimeValue2 是否有值? {nullTimeValue2.HasValue};intValue2 是否有值?{intValue2.HasValue}"); // True;False }统一判断默认值
统一判断各种类型是否为默认值,参考:c# - Check to see if a given object (reference or value type) is equal to its default - Stack Overflow
xxxxxxxxxx [Test] public void TestIsDefault() { List<int> listValue = new List<int>(); string strValue = default; int intValue = default; DateTime timeValue = default; DateTime? timeNullValue = default; DateTime? timeNullValue2 = DateTime.Now;
Console.WriteLine($"listValue 是否为默认值?{IsNullOrDefault(listValue)};strValue 是否为默认值?{IsNullOrDefault(strValue)};"); // False;True Console.WriteLine($"intValue 是否为默认值?{IsNullOrDefault(intValue)};timeValue 是否为默认值?{IsNullOrDefault(timeValue)};"); // True;True Console.WriteLine($"timeNullValue 是否为默认值?{IsNullOrDefault(timeNullValue)};timeNullValue2 是否为默认值?{IsNullOrDefault(timeNullValue2)};"); // True;False }
/// <summary> /// 统一判断是否为默认值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="argument"></param> /// <returns></returns> public bool IsNullOrDefault<T>(T argument) { // deal with normal scenarios if (argument == null) return true; if (object.Equals(argument, default(T))) return true;
// deal with non-null nullables 可空类型 Type methodType = typeof(T); if (Nullable.GetUnderlyingType(methodType) != null) return false;
// deal with boxed value types 装箱类型 Type argumentType = argument.GetType(); if (argumentType.IsValueType && argumentType != methodType) { object obj = Activator.CreateInstance(argument.GetType()); return obj.Equals(argument); }
return false; }反射中应用
准备一个类和数据,我们要一步一步演示如何将利用反射机制给类的属性进行赋值
xxxxxxxxxx public class TestCheck { public DateTime NowValue { get; set; } public DateTime? NullValue { get; set; } public DateTime? NullNowValue { get; set; } public string StrValue { get; set; } public int? NullIntValue { get; set; } public List<string> ListValue { get; set; } }
Dictionary<string, string> DataDic = new Dictionary<string, string>() { {"NowValue" ,"2008-08-08"}, {"NullValue" ,null}, {"NullNowValue" ,"2018-08-08"}, {"StrValue" ,null}, {"NullIntValue" ,null}, {"ListValue" ,null} };第一步:直接根据key,给对应的属性赋值
xxxxxxxxxx [Test] public void TestCheckNullType3() { var obj = new TestCheck(); var properties = obj.GetType().GetProperties(); foreach (var property in properties) { var valueStr = DataDic[property.Name]; property.SetValue(obj, valueStr); } }
运行时SetValue处出现类型转换异常:Object of type 'System.String' cannot be converted to type 'System.DateTime'。无法将字符串隐式转换为DateTime类型,我们要先手动转换类型再进行赋值
第二步:先手动转换为属性对应的类型再进行赋值
xxxxxxxxxx [Test] public void TestCheckNullType4() { var obj = new TestCheck(); var properties = obj.GetType().GetProperties(); foreach (var property in properties) { var valueStr = DataDic[property.Name]; // 将值转换成property对应的类型,再进行赋值 property.SetValue(obj, Convert.ChangeType(valueStr, property.PropertyType)); } }
运行时 Convert.ChangeType 处出现类型转换异常:Null object cannot be converted to a value type。Convert.ChangeType 无法处理null,我们要先手动判断一下。
第三步:在Convert.ChangeType 处理前,手动判断是否为null
xxxxxxxxxx [Test] public void TestCheckNullType5() { var obj = new TestCheck(); var properties = obj.GetType().GetProperties(); foreach (var property in properties) { var valueStr = DataDic[property.Name]; if(valueStr == null) { // 若值为null,则直接赋值null property.SetValue(obj, null); continue; } // 将值转换成property对应的类型,再进行赋值 property.SetValue(obj, Convert.ChangeType(valueStr, property.PropertyType)); } }
运行时 Convert.ChangeType 处出现类型转换异常:Invalid cast from 'System.String' to 'System.Nullable`1[[System.DateTime... 无法将字符串转换为DateTime?(可空)类型,对于可空类型我们要单独处理
第四步:判断属性是否为可空类型,并对可空类型进行转换处理
判断属性是否为可空类型:property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))
xxxxxxxxxx [Test] public void TestCheckNullType6() { var obj = new TestCheck(); var properties = obj.GetType().GetProperties(); foreach (var property in properties) { var valueStr = DataDic[property.Name]; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) { if (valueStr == null) { // 若值为null,且类型为可空类型,则直接赋值null。避免Convert.ChangeType处理null报错:Null object cannot be converted to a value type. property.SetValue(obj, null); continue; } // 将值转换成property对应类型的可空类型,再进行赋值 var nullType = property.PropertyType; NullableConverter nullableConverter = new NullableConverter(nullType); nullType = nullableConverter.UnderlyingType; property.SetValue(obj, Convert.ChangeType(valueStr, nullType)); } else { // 将值转换成property对应的类型,再进行赋值 property.SetValue(obj, Convert.ChangeType(valueStr, property.PropertyType)); } } }执行后,基本可以满足需求。关于反射能想到的就这些,后面遇到再补充了。
