检查字符串是否是编译时已知的文字字符串?

我正在写一个库,我有一个接受字典的方法。 字典的值是不受信任的/不安全的,但密钥是可信的,如果最终用户能够输入任意密钥名称,则可能发生“坏事”。

所以当其他开发人员使用这个库函数时,我想强制他们在编译时知道密钥名称。 所以这样的事情是允许的:

string userInput = Console.ReadLine(); Dictionary something = new Dictionary(); something.Add("MyKey", userInput); 

因为“MyKey”是一个字符串文字,在编译时就知道了。 但是这样的事情会引发编译或运行时exception:

 string userInput = Console.ReadLine(); string userKey = Console.ReadLine(); Dictionary something = new Dictionary(); something.Add(userKey, userInput); 

因为用户输入用于键(userKey),因此在编译时它是未知的。

我查看了GetType(),并没有什么能真正区分文字字符串和在运行时创建的字符串。

我可以想到两种可能的方式,都使用reflection:

  • 接收包含键作为字符串常量字段的类型。 关于如何从类型中获取常量字段还有另一个答案 。

  • 定义存储字符串的属性。 属性参数必须是常量表达式。 您可以接收使用该属性修饰的类型或成员,并从所述属性中提取密钥。


值得一提的是,两者都可能被客户端代码伪造。 例如,可以使用动态程序集或System.ComponentModel.TypeDescriptor类。

您可以使用string.IsInterned来确定字符串是否已被实现。 默认情况下,所有编译时文字都将被中断,但您可以使用编译器标志将其关闭。

这是运行时检查,而不是编译时检查。

另请注意,非编译时文字可以使用string.Intern函数实现,因此从技术上讲,您可以让非文字字符串通过测试。 但是如果你的程序要么在任何时候都没有实习字符串,要么只有你知道的实际字符串是安全的,那么这可能有效。


另一个选择,如果你的所有密钥都应该在编译时知道,那就是不要让密钥成为字符串。 例如,使键成为枚举,以便您知道可以用作键的唯一值位于您在编译时修复的列表中。

没有经过广泛测试,但通过使用表达式而不是直接值,您可以测试传递的值的类型。 例如

 void Add(Expression> fn) { if (fn.Body.NodeType != ExpressionType.Constant) throw new ArgumentException("Only literal strings are allowed"); //and extra check if the value itself is interned var val = fn.Compile()(); if (string.IsInterned(val) == null) throw new ArgumentException("Only literal strings are allowed"); } 

然后开发人员必须将参数作为lambda传递:

  Add(() => "Test"); //Valid string somestring = "Test"; Add(() => somestring); //Error