【问题标题】:Convert JavaScript "Key=>Value" to C#将 JavaScript "Key=>Value" 转换为 C#
【发布时间】:2021-10-27 22:07:34
【问题描述】:

对于这个可能很愚蠢的问题,我深表歉意。 有没有一种简单的方法可以在 C# 中定义具有多个级别和值类型的 key => value 数组?

我在这里有一个 javascript 示例:

let keyValueArray = {
    key1: {
        key11: {
            key111: "String1",
            key112: "String2",
            key113: "String3"
        },
        key12: {
            key121: "String4"
        }
    },
    key2: {
        key21: {
            key211: 1234 //int
        }
    },
    key3: "String5"
}

我希望能够得到这样的值: var value = keyValueArray.key1.key11.key111;

我尝试了很多,但没有成功。

谢谢大家

【问题讨论】:

  • 您的“键值”集合缺少实际键,您知道。 key11 下有这组字符串值,但没有与它们关联的实际键。这看起来更像是某种树节点。我的意思是,key1 不是真正的密钥;它是key11key12 下所有内容的集合。

标签: javascript c#


【解决方案1】:

这个问题涉及到 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 将我引向那篇文章。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    • 2015-03-22
    • 1970-01-01
    • 1970-01-01
    • 2012-02-06
    • 2022-06-12
    • 2012-01-08
    相关资源
    最近更新 更多