这个问题涉及到 JavaScript 与包括 C# 在内的许多其他面向对象语言之间根本区别的核心。
C# 是静态类型的,这意味着(几乎 - 我会回到这个)每个对象都有一个在编译时固定的类型,该类型有一组固定的方法和属性,并且任何尝试引用不属于类型定义的方法或属性会导致编译器错误。你可以做一些事情,比如将一个变量声明为object,然后在其中存储一个派生类型的实例,例如DateTime。如果你这样做,那么调用对象的GetType() 方法(继承自object)会告诉你对象的运行时类型是System.DateTime。如果要使用对象的DateTime 方法和属性,则首先需要将object 转换为DateTime。
var myDate = (DateTime)myObject;
Console.WriteLine myDate.Year;
但是 C#(通常,我会讲到这个)不允许您在声明对象后更改其类型,也不允许您在运行时添加属性。
相比之下,JavaScript 对待对象的方式完全不同。任何不是primitive types(字符串、数字等)之一的东西都只是object,您可以在运行时添加和删除属性和方法。尝试获取不存在的属性的值不会引发运行时错误,而只会返回值undefined。这使得 JavaScript 非常强大,但也容易在属性名称中出现拼写错误,从而导致运行时错误,大意是 undefined 没有 doSomething 方法。
那么,如何在 C# 中创建和使用这种树形数据结构呢?我会给你 4 种可能的方法。
System.Collections.Generic.Dictionary
我的第一个想法是使用以string 为键和object 作为值的字典 - 这允许您将值设置为字符串、整数或任何您想要的,包括另一个字典,它允许您构建树结构。
public static void UseDictionary()
{
Console.WriteLine("Using Dictionary<string, object>");
var key11 = new Dictionary<string, object>
{
{ "key111", "String1" },
{ "key112", "String2" },
{ "key113", "String3" }
};
var key12 = new Dictionary<string, object>
{
{ "key121", "String4" }
};
var key1 = new Dictionary<string, object>
{
{ "key11", key11 },
{ "key12", key12 }
};
var key21 = new Dictionary<string, object>
{
{ "key211", 1234 }
};
var key2 = new Dictionary<string, object>
{
{ "key21", key21 }
};
var keyValueArray = new Dictionary<string, object>
{
{ "key1", key1 },
{ "key2", key2 },
{ "key3", "String5" }
};
var something = ((Dictionary<string, object>)((Dictionary<string, object>)keyValueArray["key1"])["key11"])["key111"];
Console.WriteLine(something);
var k1 = (Dictionary<string, object>)keyValueArray["key1"];
var k11 = (Dictionary<string, object>)k1["key11"];
var k111 = k11["key111"];
Console.WriteLine(k111);
}
这可行,但远非理想。特别是,获取嵌套在树中很深的东西的值是丑陋且难以阅读的。
var something = ((Dictionary<string, object>)((Dictionary<string, object>)keyValueArray["key1"])["key11"])["key111"];
甚至为了可读性而将其分解为几个操作也相当笨拙
var k1 = (Dictionary<string, object>)keyValueArray["key1"];
var k11 = (Dictionary<string, object>)k1["key11"];
var k111 = k11["key111"];
Console.WriteLine(k111);
System.Dynamic.ExpandoObject
在这里我们遇到了dynamic 关键字。使用dynamic 而不是var 初始化变量会告诉编译器不要在编译时检查属性是否存在,从而允许您在运行时向对象添加属性。这更接近于 JavaScript 的行为方式。这样做的结果(无论如何在我的 Visual Studio 安装中)是它还会关闭该变量的智能感知和自动完成功能。
public static void UseExpandoObject()
{
Console.WriteLine("Using ExpandoObject");
dynamic key11 = new ExpandoObject();
key11.key111 = "String1";
key11.key112 = "String2";
key11.key113 = "String3";
dynamic key12 = new ExpandoObject();
key12.key121 = "String4";
dynamic key1 = new ExpandoObject();
key1.key11 = key11;
key1.key12 = key12;
dynamic key21 = new ExpandoObject();
key21.key211 = 1234;
dynamic key2 = new ExpandoObject();
key2.key21 = key21;
dynamic keyValueArray = new ExpandoObject();
keyValueArray.key1 = key1;
keyValueArray.key2 = key2;
keyValueArray.key3 = "String5";
Console.WriteLine(keyValueArray.key1.key11.key111);
}
这是一个很大的改进,尤其是检索深度嵌套键值的语法,尽管创建树仍然相当笨重。
System.Dynamic.ExpandoObject 与 JSON
假设大多数时候您的数据将来自文件、数据库、Web 服务或其他一些外部源,而不是嵌入到您的代码中,您现在可以利用 JSON 反序列化来初始化您的对象。在这种情况下,我只是将您的 JSON 复制到一个名为 data.json 的文件中。
我使用Newtonsoft.Json nuget 包进行反序列化 - 这只是个人喜好,还有其他方法可以将 JSON 反序列化为对象。
public static void UseExpandoObjectWithJson()
{
Console.WriteLine("Using ExpandoObject with JSON");
var json = File.ReadAllText("data.json");
dynamic keyValueArray = JsonConvert.DeserializeObject<ExpandoObject>(json);
Console.WriteLine(keyValueArray.key1.key11.key111);
}
很好,但它的行为仍然不完全像 JavaScript - 如果您尝试引用 ExpandoObject 的不存在属性,它将引发运行时错误。要让不存在的属性返回null(可能是最接近undefined 的C#)而不是抛出错误,您需要创建自己的DynamicObject 抽象类实现。
带有 JSON 的 System.Dynamic.DynamicObject
public class Node : DynamicObject
{
private Dictionary<string, object> properties = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (properties.ContainsKey(binder.Name))
{
result = properties[binder.Name];
return true;
}
else
{
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
properties[binder.Name] = value;
return true;
}
}
用法:
public static void UseDynamicObjectWithJson()
{
Console.WriteLine("Using DynamicObject with JSON");
var json = File.ReadAllText("data.json");
dynamic keyValueArray = JsonConvert.DeserializeObject<Node>(json);
Console.WriteLine(keyValueArray.key1.key11.key111);
}
在尝试访问其属性之前,请小心测试属性值是否返回 null,就像在 JavaScript 中如果有可能是 undefined 时需要这样做一样
进一步阅读
Very good article on the dynamic keyword, ExpandoObject and DynamicObject 并感谢 this answer 将我引向那篇文章。