从JSON文件中解析c#函数

本文关键字:函数 JSON 文件 | 更新日期: 2023-09-27 18:07:23

我正在摆弄游戏道具执行,为了避免硬编码道具,我想从外部文件加载它们的信息和执行。

基本格式是这样的:

-- Items.json
[
    {
        "name": "Rusty Key",
        "shortDesc": "A rusty key.",
        "longDesc": "A slightly rusty key. You don't know what door it opens.",
        "code": "RustyKey.cs"
    }
]

或者直接将Use()代码嵌入JSON文件:

-- Items.json
[
    {
        "name": "Rusty Key"
        ...
        "use": "return 0;"
    }
]

JSON文件将在运行时加载,并动态添加项目及其实现。

这也可能演变成一种简单的脚本语言,允许玩家创建自己的道具,但在目前的形式下,我不会向公众发布它,因为有可能被滥用。

所以我的问题是:我如何加载/解析附加的脚本文件或嵌入代码,并将其附加到动态创建的对象?

从JSON文件中解析c#函数

创建MyLibrary.dll并添加以下代码:

namespace MyTypesNamespace
{
    public abstract class BaseItem
    {
        public abstract bool Use(object onMyObject);
    }
    public class Door
    {
        public bool IsLocked { get; set; }
        public bool Open()
        {
            if (IsLocked)
            {
                System.Console.WriteLine("Cannot open door. It is locked!");
                return false;
            }
            //Some code
            System.Console.WriteLine("Door is opened!");
            return true;
        }
    }
}

在主项目中,添加对MyLibrary.dll的引用和以下方法:

private static List<BaseItem> loadItems(string fromCode)
{
    CodeDomProvider codeProvider = new CSharpCodeProvider();
    // add compiler parameters
    CompilerParameters compilerParams = new CompilerParameters();
    compilerParams.CompilerOptions = "/target:library /optimize";
    compilerParams.GenerateExecutable = false;
    compilerParams.GenerateInMemory = true;
    compilerParams.IncludeDebugInformation = false;
    compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
    compilerParams.ReferencedAssemblies.Add("System.dll");
    compilerParams.ReferencedAssemblies.Add("MyLibrary.dll");
    // compile the code
    CompilerResults results = codeProvider.CompileAssemblyFromSource(compilerParams, fromCode);
    var items = new List<BaseItem>();
    foreach (var itemType in results.CompiledAssembly.DefinedTypes)
    {
        ConstructorInfo ctor = itemType.GetConstructor(Type.EmptyTypes);
        object instance = ctor.Invoke(null);
        items.Add(instance as BaseItem);
    }
    return items;
}

用法:

private static void Main()
{
    string code =  loadCode();
    List<BaseItem> items = loadItems(code);
    BaseItem rustyKey = items[0];
    BaseItem unlockAnyDoor = items[1];
    Door myDoor = new Door { IsLocked = true };
    rustyKey.Use(myDoor);
    unlockAnyDoor.Use(myDoor);
    rustyKey.Use(myDoor);

    Console.ReadLine();
}
private static string loadCode()
{
    return @"
        using MyTypesNamespace;
        public class RustyKey : BaseItem
        {
            public override bool Use(object onMyObject)
            {
                var door = onMyObject as Door;
                return door.Open();
            }
        }
        public class UnlockAnyDoor : BaseItem
        {
            public override bool Use(object onMyObject)
            {
                var door = onMyObject as Door;
                door.IsLocked = false;
                System.Console.WriteLine(""Door is unlocked!"");
                return true;
            }
        }
";
}

输出:(见在线输出)

无法打开门。门锁上了!门没锁!门开了!之前
编辑:

您可以通过消除代码重复来简化将来的代码提供(将从文件中读取)。让generateItemClass方法取classNameUse()方法体:

private static string generateItemClass(string className, string useBody)
{
    return $@"
        public class {className} : BaseItem
        {{
            public override bool Use(object onMyObject)
            {{
                {useBody}
            }}
        }}";
}

不要忘记为所有类添加using MyTypesNamespace;

private static string loadCode()
{
    string namespaces = @"using MyTypesNamespace;";
    string rustyKeyClass = generateItemClass("RustyKey",
        @"var door = onMyObject as Door;
        return door.Open();");
    string unlockAnyDoorClass = generateItemClass("UnlockAnyDoor",
        @"var door = onMyObject as Door;
        door.IsLocked = false;
        System.Console.WriteLine(""Door is unlocked!"");
        return true;");
    return namespaces + rustyKeyClass + unlockAnyDoorClass;
}

将原始代码放入JSON意味着必须拾取该代码,可能对其进行解析,将其粘合到某些代码外壳中,编译它并检查错误。那是很多机器。(另一个答案提供了这样做的细节)。

还有一个问题,如果有人可以在那里编写任意代码,他们就会这样做,并且他们会以任意奇怪的方式编写一些东西来破坏你的应用程序,要么造成安全漏洞,要么造成一些困难的调试问题。

我认为在大多数情况下,你最好只是在JSON中标记你想要的内容,例如,

-- Items.json
[
    {
        "name": "Rusty Key"
        ...
        "result": "0"
    }
]

现在程序逻辑读取JSON文件,存储<"RustyKey","0">放入散列查找集合中,然后当出现对RustyKey的请求时,在集合中查找结果值。

当然,你不能像include代码那样做那么多复杂的事情,但是你会遇到更少的麻烦。

格言:"eval is evil"