C# LINQ中字符串与Object类型比较的异常行为解析请求
C# Object类型属性字符串比较异常解析
问题场景
创建Test类,其Name属性为object类型,通过直接赋值、JSON反序列化、克隆、反射创建等方式生成多个实例后,使用LINQ的Where(a=>a.Name=="test")筛选时,仅直接赋值、克隆直接赋值实例、反射创建的实例能匹配;JSON反序列化及克隆它的实例虽显示Name值为"test",却无法匹配,最终匹配数为4(预期6个)。
示例代码
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace TestJsonDeser { internal class Program { public class Test { public object Name { get; set; } public object Value { get; set; } } static void Main(string[] args) { Test test0 = new Test() { Name = "test", Value = 1 }; var ts = JsonConvert.SerializeObject(test0); var test1 = JsonConvert.DeserializeObject<Test>(ts); var test2 = Clone(test0); var test3 = Clone(test1); var test4 = CreateTest(); var test5 = Clone(test4); List<Test> list = new List<Test>() { test0, test1, test2, test3, test4, test5 }; var r3 = list.Where(a=>a.Name=="test").ToList(); Console.WriteLine($"Count:{r3.Count}"); foreach (var r in r3) Console.WriteLine($"Idx:{list.IndexOf(r)}"); Console.ReadKey(); } public static T Clone<T>(T source) where T : class, new() { if (source == null) return null; var tp = typeof(T); var ret = Activator.CreateInstance(typeof(T)); foreach (var prop in tp.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(a => a.CanWrite && a.CanRead)) { var value = prop.GetValue(source, null); if (value is string str) prop.GetSetMethod().Invoke(ret, new object[] { str.Clone() }); if (value is long ln) prop.GetSetMethod().Invoke(ret, new object[] { ln }); } return (T)ret; } static Test CreateTest() { var test = Activator.CreateInstance(typeof(Test)); typeof(Test).GetProperty("Name").SetValue(test, "test", null); typeof(Test).GetProperty("Value").SetValue(test, 1, null); return (Test)test; } } }
输出结果
Count:4 Idx:0 Idx:2 Idx:4 Idx:5
原因分析
这不是LINQ或Newtonsoft.Json的Bug,而是C#类型系统和JSON序列化特性共同作用的结果:
- 直接赋值/反射创建的实例:
Name属性存储的是string类型实例,加上C#字符串驻留机制,"test"是同一个引用。==对引用类型默认比较引用地址,因此能匹配。 - JSON反序列化的实例:Newtonsoft.Json默认将JSON字符串反序列化为
JValue(属于JToken家族)而非原生string类型。虽然JValue.ToString()输出是"test",但实际类型不是string。此时a.Name=="test"是用JValue实例和string实例做引用比较,自然不相等,无法匹配。 - 克隆JSON反序列化实例:
Clone方法仅对string类型做克隆,而test1.Name是JValue,不满足value is string str的判断,克隆时直接复制了JValue的引用,同样无法通过==和字符串"test"匹配。
解决方案
可采用以下任意一种方式修复:
- 使用
Equals做值比较:替换筛选条件为a.Name.Equals("test"),Equals会调用对应类型的重写方法,JValue和string的Equals都会做值比较。 - 转换为
string后比较:a.Name?.ToString() == "test",确保两边都是string类型,此时==会做值比较。 - 配置JSON序列化行为:通过自定义设置让Newtonsoft.Json将JSON字符串反序列化为原生
string类型,例如:
注:var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; var test1 = JsonConvert.DeserializeObject<Test>(ts, settings);TypeNameHandling会在JSON中添加类型信息,仅在可控场景下使用,避免安全风险。
内容的提问来源于stack exchange,提问作者Cirrosi
相关产品推荐
相关产品推荐

