C# 编写命令行。第 2 部分:特性参数。






2.33/5 (3投票s)
2003 年 8 月 6 日
4分钟阅读

44817

414
访问格式化为特性参数列表的命令行数据。
引言
显然,程序以 Main(string[] args)
开始,其中 args
通常需要以某种方式进行解析和评估。有很多解决方案可以处理这个简单但有时乏味且缺乏创意的任务。本文的 第 1 部分和第 2 部分提出了另外两种不同的解决方案。其特点是它们不解析任何内容,而是完全基于 C# 和 .NET 的功能。
在 第 1 部分中,命令行参数被视为访问程序内容的 C# 代码片段。命令行成为程序的一个真实组成部分,并且用 C# 编写,就像一个程序一样。这种方法对开发人员来说非常强大,尤其是在调试和测试方面。但对于最终用户来说,这可能很不方便甚至不安全。
第 2 部分提出了一种专为最终用户设计的解决方案。命令行看起来几乎像传统的命令行。一个或多或少“传统的”命令行是什么样的?假设它有一定数量的必需位置参数和一定数量的可选命名参数。这让人联想到 C# 特性参数列表,并引发了一个想法:让命令行由一个参数组成,该参数实际上是特性参数的列表。我们的目标是以最少的编程工作量来访问数据。
步骤 1:输入数据设计
让我们通过一个假设的示例程序(附带源代码)来说明。假设我们的程序需要以下输入数据:
- 必需的字符串值(例如,某个目录名);
- 必需的位字段(例如,要执行的某些作业的标志);
- 某个可选的
int
值; - 某个可选的
bool
值; - 某个可选的
double
值。
我们将为 Main()
中的 args[0]
值使用特性参数样式。
args[0]
值的示例
null, Jobs.Job1|Jobs.Job2
@"C:\Temp", Jobs.Job2|Jobs.Job3, MyDouble = 3.14
@"C:\Temp", Jobs.Job3, MyBool = true, MyDouble = 3.14, MyInt = 123
注意:命令行只包含一个参数,该参数必须完全包含在引号(")中,并且任何内部引号都必须前面加上反斜杠(\)。这些要求来自命令行规则。
对应于上面 args[0]
的命令行
"null, Jobs.Job1|Jobs.Job2"
"@\"C:\Temp\", Jobs.Job2|Jobs.Job3, MyDouble = 3.14"
"@\"C:\Temp\", Jobs.Job3, MyBool = true, MyDouble = 3.14, MyInt = 123"
步骤 2:输入数据类
设计好输入数据后,我们创建一个类来保存它们。类 InputAttribute
继承自 System.Attribute
。必需的数据(目录名和作业标志)实现为只读属性,并添加了一个带这些参数的构造函数。可选数据实现为读/写属性;在实际程序中,它们的名称必须经过深思熟虑,以充分描述其含义。用户在命令行中使用的正是这些名称。最后,我们定义 enum Jobs
,其值也用于命令行。
namespace Test
{
// This enum is used in the command line
[FlagsAttribute()]
public enum Jobs { Job1 = 1, Job2 = 2, Job3 = 4 }
// Command line data are evaluated as this class field values
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class InputAttribute : System.Attribute
{
// Constructor: 'directory' and 'jobs'
// are positional and required arguments
public InputAttribute(string directory, Jobs jobs)
{
this.directory = directory;
this.jobs = jobs;
}
// Required positional: 1st argument
public string Directory
{
get { return directory; }
}
// Required positional: 2nd argument
public Jobs Jobs
{
get { return jobs; }
}
// Optional named: MyInt=...
public int MyInt
{
get { return myInt; }
set { myInt = value; }
}
// Optional named: MyBool=...
public bool MyBool
{
get { return myBool; }
set { myBool = value; }
}
// Optional named: MyDouble=...
public double MyDouble
{
get { return myDouble; }
set { myDouble = value; }
}
// These data are required by constructor
private string directory;
private Jobs jobs;
// These data are optional with some default values
private int myInt = 0;
private bool myBool = false;
private double myDouble = 0.0;
}
}
步骤 3:输入数据对象
现在我们可以编写唯一的代码语句来获取命令行数据了。
InputAttribute input = (InputAttribute)Attributer.Evaluate(
typeof(InputAttribute), args[0], "using Test;");
其中
typeof(InputAttribute)
是我们的特性类的类型;args[0]
是我们程序的命令行参数;"using Test;"
用于简洁起见,允许在命令行中写入"Jobs.Job1"
而不是"Test.Jobs.Job1"
。
代码
类 Absolute.Attributer
是一个特性评估器(附带源代码)。它是一个“静态类”,只有一个 static
方法 Evaluate()
。该方法执行以下三个小步骤:
- 生成一个程序集源代码。它包含
using
指令(如果需要)、一个程序集特性,没有其他代码。生成的源代码示例:using Test; [assembly:InputAttribute(@"C:\Temp", Jobs.Job1, MyDouble = 3.14, MyBool = true)]
- 程序集源代码被编译,并在内存中创建程序集。此程序集除我们的
InputAttribute
特性外,不包含任何内容。 - 请求并返回一个
InputAttribute
实例。此实例包含所有已评估并准备就绪的数据。
关于安全性的说明
与本文 第 1 部分(执行来自命令行的 C# 代码片段)不同,这种方式对最终用户来说似乎是安全的。根据定义,特性参数不能是非常量表达式。如果一个不小心或恶意的非常量表达式被写成特性参数,它将无法编译。步骤 2(参见背景)将在编译时失败并抛出异常。您可以尝试为我们的示例程序编写不同的“不良”命令行,运行它并查看会发生什么。您将收到一个错误消息,程序将停止。尽管如此,是否有人能找到这里的漏洞?
摘要
附带的项目包含两个文件:Attributer.cs(实用类)和 Class1.cs(此处描述的示例程序)。我希望这个简单的实用程序能帮助一些人在自己的程序中避免不具创造性的命令行解析工作。欢迎提出任何意见。