通过反射获取可选 Guid 的默认值?

Getting DefaultValue for optional Guid through reflection?

我有以下代码,用作说明不同场景的示例:

    public static void MethodWithOptionalGuid(Guid id = default(Guid)) { }

    public static void MethodWithOptionalInteger(int id = 2) { }

    public static void MethodWithOptionalString(string id = "33344aaa") { }

    public static void MethodWithoutOptionalParameter(int id, Guid longId) { }

    static void Main(string[] args)
    {
        var methods = typeof(Program).GetMethods(BindingFlags.Public | BindingFlags.Static).ToList();

        foreach (var method in methods)
        {
            PrintMethodDetails(method);
        }

        Console.ReadLine();
    }

    static void PrintMethodDetails(MethodInfo method)
    {
        Console.WriteLine(method.Name);

        foreach (var parameter in method.GetParameters().ToList())
        {
            Console.WriteLine(parameter.Name +
                              " of type " + parameter.ParameterType.ToString() +
                              " with default value:" + parameter.DefaultValue);
        }

        Console.WriteLine();
    }

它打印以下内容:

MethodWithOptionalGuid
id of type System.Guid with default value:

MethodWithOptionalInteger
id of type System.Int32 with default value:2

MethodWithOptionalString
id of type System.String with default value:33344aaa

MethodWithoutOptionalParameter
id of type System.Int32 with default value:
longId of type System.Guid with default value:

最后 3 种方法的输出似乎没问题。

我的问题是关于第一个问题,MethodWithOptionalGuid:为什么无法识别 Guid 的默认值?

我希望收到类似“0000000-...”的信息。我还尝试使用 new Guid() 和相同的结果初始化可选参数。我也尝试了其他结构,例如 TimeSpan,并且行为是相同的。

我预计所有值类型的行为都相同(如整数示例所示)。

额外:我在尝试在 Asp.Net MVC 中使用带有可选 Guid 参数的操作时发现了这个东西,但失败了(必须使 Guid 可以为空)。浏览 MVC 代码,发现它在某些时候使用了 DefaultValue。所以我做了这个代码示例来更好地说明我的问题。

抱歉 - 我无法告诉您为什么它没有 return 正确的值,但我有一个使用此扩展方法的解决方法:

public static object GetDefaultValue(this ParameterInfo p)
{
    if (!p.Attributes.HasFlag(ParameterAttributes.HasDefault))
    {
        return null;
    }

    if (p.DefaultValue != null || p.RawDefaultValue != null)
    {
        return p.DefaultValue ?? p.RawDefaultValue;
    }

    return p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null;
}

也许会有帮助。

使用您选择的反编译器打开您的测试文件,您将看到您的代码实际上被编译为:

public static void MethodWithOptionalGuid(Guid id = null) { }

这就是为什么 DefaultValue 属性 returns 为空。 当您现在使用此参数时,假设您添加:

Console.WriteLine(id);

编译器会插入一个

box [mscorlib]System.Guid

WriteLine 之前的语句。现在输出将是预期的“0000000-...”

为什么 非常相关,当您想做这样的事情时,您确实需要注意这一点。龙住在这里。如果您使用 ildasm.exe 查看该方法,那么您会看到:

  .param [1] = nullref

structure 类型的默认值为 null,呃哦。 .param 指令的语义在 CLI 规范 Ecma-335 第 II.15.4.1.4 章中进行了描述。它很短,我将复制粘贴整个章节(为了便于阅读而编辑)

MethodBodyItem ::= .param ‘[’ Int32 ‘]’ [ ‘=’ FieldInit ]

This directive stores in the metadata a constant value associated with method parameter number Int32, see §II.22.9. While the CLI requires that a value be supplied for the parameter, some tools can use the presence of this attribute to indicate that the tool rather than the user is intended to supply the value of the parameter. Unlike CIL instructions, .param uses index 0 to specify the return value of the method, index 1 to specify the first parameter of the method, index 2 to specify the second parameter of the method, and so on.

[Note: The CLI attaches no semantic whatsoever to these values—it is entirely up to compilers to implement any semantic they wish (e.g., so-called default argument values). end note]

[注释]最相关。当你使用反射时,你扮演了编译器的角色,正确解释默认值取决于你。换句话说,你必须知道在 C# 中结构类型的默认值是 null.

否则,这是对 [FieldInit] 中可以存储的内容的限制。初始化值必须存储在程序集的元数据中,并受到与 [attribute] 声明相同类型的限制。只能使用简单的值类型,不能使用结构。因此,按照约定,C# 设计人员通过使用 null 来解决这个问题。足以让 C# 编译器理解 default(Guid) 的意图。

问题还不止于此,还有其他方法可以指示可选参数,[OptionalAttribute] 在 C# v4.0 之前使用。并且今天仍然被 COM 互操作库和其他语言(如 VB.NET 和 C++/CLI)使用。那是龙居住的地方。

暂时忽略那些野兽,你所拥有的解决方法代码可能如下所示:

    var def = parameter.DefaultValue;
    if (parameter.ParameterType.IsValueType && def == null) {
        def = Activator.CreateInstance(parameter.ParameterType);
    }
    Console.WriteLine(parameter.Name +
                      " of type " + parameter.ParameterType.ToString() +
                      " with default value:" + def);

我想答案可以在 c# 语言规范中找到(有人可能会找到更相关的东西)

要点 4.1.2

When the operands of an expression are all simple type constants, it is possible for the compiler to evaluate the expression at compile-time. Such an expression is known as a constant-expression (§7.19). Expressions involving operators defined by other struct types are not considered to be constant expressions.

Through const declarations it is possible to declare constants of the simple types (§10.4). It is not possible to have constants of other struct types, but a similar effect is provided by static readonly fields.

即使我们在这里不讨论表达式或常量,似乎也只能在编译时评估/计算简单类型(如 int 的结构)。这就是为什么 default(int) 可以被计算然后在你的代码中显示 0 的原因。

但是其他结构无法在编译时计算,所以我想说这就是它们被评估为 null.

的原因

设置默认参数值时,它必须是编译时常量。

棘手的部分是您可以将 default(Guid)new Guid() 作为可接受的编译时常量(但评估为 null),但是......它不能成为常量。

例如,你不能做

const Guid g = new Guid();