【问题标题】:Getting DefaultValue for optional Guid through reflection?通过反射获取可选 Guid 的 DefaultValue?
【发布时间】:2016-03-23 08:44:13
【问题描述】:

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

    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。所以我制作了这个代码示例来更好地说明我的问题。

【问题讨论】:

  • @vc74 我更新了我的问题。虽然我同意最后 3 种方法的输出符合我的预期,但我无法理解第一种方法的输出。
  • 我认为OP的问题主要是关于MethodWithOptionalGuid。指定了一个默认值,我也想知道为什么default(Guid) 不会导致“0000..”
  • @RaphaëlAlthaus 它还说默认(Guid)| new Guid() 是编译器已知的值,它应该可以工作。根本原因解释了为什么您不能使用 Guid.Empty。
  • @RaphaëlAlthaus 我不明白:代码编译得很好,如果你在MethodWithOptionalGuid 中输出id 的值,它 is "00000-".. . 那么为什么parameter.DefaultValuenull 而不是Guid.Emtpy

标签: c# reflection optional-parameters value-type base-class-library


【解决方案1】:

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

  .param [1] = nullref

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

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

该指令在元数据中存储一个与方法参数号 Int32 关联的常量值, 见§II.22.9。虽然 CLI 要求为参数提供一个值,但某些工具可以使用 此属性的存在表明该工具而不是用户旨在提供值 参数。与 CIL 指令不同,.param 使用索引 0 来指定方法的返回值, index 1 指定方法的第一个参数, index 2 指定方法的第二个参数 方法等等。

[注意:CLI 对这些值不附加任何语义——这完全取决于编译器 实现他们希望的任何语义(例如,所谓的默认参数值)。尾注]

[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);

【讨论】:

  • 我们应该说所有不能是 const 的东西都将被评估为 null 吗?所以只有 simple typesenum typesstring 不会被评估为 null ? (default(strting) 的特殊情况是 string 是 ref 类型,当然是 null)
  • 这只是调用了另一条龙。你不能声明一个 const Guid 但你可以告诉它作为默认值可以正常工作。所以不,你不能那样说。
【解决方案2】:

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

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

这就是 DefaultValue 属性返回 null 的原因。 当您现在使用此参数时,假设您添加:

Console.WriteLine(id);

编译器会插入一个

box [mscorlib]System.Guid

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

【讨论】:

    【解决方案3】:

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

    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;
    }
    

    也许会有所帮助。

    【讨论】:

    • 谢谢你的答案。不幸的是,我对 WHY 更感兴趣。我已经更新了我的问题,解释了我是如何来到这里的。再次感谢。
    【解决方案4】:

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

    第 4.1.2 点

    当表达式的操作数都是简单类型常量时,它 编译器可以在 编译时。这样的表达式称为常量表达式 (第 7.19 节)。涉及由其他结构类型定义的运算符的表达式 不被视为常量表达式。

    通过 const 声明可以声明 简单类型(§10.4)。不可能有其他的常数 struct 类型,但 static readonly 提供了类似的效果 字段。

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

    但是其他结构不能在编译时计算,所以我想说这就是为什么它们被评估为null

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

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

    例如,你不能这样做

    const Guid g = new Guid();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-14
      • 1970-01-01
      • 2015-05-15
      • 2013-04-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多