通过反射获取可选Guid的DefaultValue?

时间:2016-03-23 08:44:14

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

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

    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 -...”的内容。我还尝试使用新的Guid()和相同的结果初始化可选参数。我也试过其他结构,比如TimeSpan,行为是一样的。

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

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

4 个答案:

答案 0 :(得分:5)

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

  .param [1] = nullref

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不会对这些值附加任何语义 - 这完全取决于编译器   实现他们希望的任何语义(例如,所谓的默认参数值)。结束说明]

[注意]是最相关的。当您使用Reflection时,您将扮演编译器的角色,由您来正确解释默认值。换句话说,您必须知道在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);

答案 1 :(得分:2)

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

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

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

Console.WriteLine(id);

编译器将插入

box [mscorlib]System.Guid
WriteLine之前的

语句。现在输出将是预期的" 0000000 -..."

答案 2 :(得分:1)

抱歉 - 我不能告诉你为什么它没有返回正确的值,但是我使用这种扩展方法进行了一次工作:

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

也许会有所帮助。

答案 3 :(得分:1)

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

第4.1.2点

  

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

     

通过const声明,可以声明的常量   简单类型(§10.4)。其他常量不可能   结构类型,但静态只读提供了类似的效果   字段。

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

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

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

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

例如,你无法做到

const Guid g = new Guid();