2016-03-23 15 views
3

Ich habe den folgenden Code, dass ich als Probe verwenden verschiedene Szenarien zur Veranschaulichung:DefaultValue für optionale Guid durch Reflektion erhalten?

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

Er druckt die folgenden:

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: 

Der Ausgang für die letzten drei Methoden in Ordnung zu sein scheint.

Meine Frage ist in Bezug auf die erste, MethodWithOptionalGuid: warum der Vorgabewert des Guid nicht erkannt wird?

Ich erwartete so etwas wie "0000000 -..." zu erhalten. Ich versuchte auch, den optionalen Parameter mit neuer Guid() und demselben Ergebnis zu initialisieren. Ich habe auch andere Strukturen, wie TimeSpan versucht und das Verhalten ist das gleiche.

ich erwartete, dass alle Werttypen gleich verhalten würden (wie in integer Beispiel zu sehen).

Extras: Ich fand diese Sache bei dem Versuch, in Asp.Net MVC eine Aktion mit einem optionalen Guid Parametern zu verwenden und fehlgeschlagen (hatte die Guid nullable zu machen). Ging durch MVC-Code und festgestellt, dass es den DefaultValue an einem bestimmten Punkt verwendet. Also habe ich dieses Codebeispiel gemacht, um mein Problem besser zu veranschaulichen.

+0

@ vc74 nicht ich meine Frage aktualisiert. Während ich zustimme, dass für die letzten 3 Methoden, die Ausgabe ist, was ich erwartet habe, habe ich Schwierigkeiten, die Ausgabe für die erste Methode zu verstehen. –

+0

Ich denke, dass die Frage von OP hauptsächlich 'MethodWithOptionalGuid' betrifft. Dort wird ein Standardwert angegeben und ich frage mich auch, warum 'default (Guid)' nicht zu "0000 .." führt. –

+0

@ RaphaëlAlthaus Dort steht auch, dass default (Guid) | new Guid() ist ein Compiler bekannter Wert und es sollte funktionieren. Die Grundursache erklärt, warum Sie Guid.Empty nicht verwenden können. –

Antwort

5

Die warum ist ziemlich relevant und etwas, das Sie wirklich beachten müssen, wenn Sie so etwas tun möchten. Drachen leben hier. Wenn Sie ildasm.exe verwenden, um die Methode zu suchen, dann werden Sie sehen:

.param [1] = nullref 

A null als Standard für eine Struktur Typ, uh-oh. Die Semantik für die .param-Direktive ist in der CLI-Spezifikation Ecma-335, Kapitel II.15.4.1.4 beschrieben. Es ist sehr kurz, ich werde das ganze Kapitel (bearbeitet für Lesbarkeit) copy-paste

MethodBodyItem :: = .param '[' Int32 ']' [ '=' FieldInit]

Diese Richtlinie speichert in den Metadaten ein konstanter Wert, der mit der Methodenparameternummer Int32, verbunden ist, siehe §II.22.9.Während die CLI erfordert, dass ein Wert für den Parameter bereitgestellt wird, können einige Tools die Präsenz dieses Attributs verwenden, um anzuzeigen, dass das Tool und nicht der Benutzer den Wert des Parameters angeben soll. Im Gegensatz zu CIL-Anweisungen verwendet .param den Index 0 zur Angabe des Rückgabewerts der Methode, Index 1 zur Angabe des ersten Parameters der Methode, Index 2 zur Angabe des zweiten Parameters der -Methode und so weiter.

[Anmerkung: Die CLI fügt diesen Werten keinerlei semantische Bedeutung bei - es ist völlig Sache der Compiler, jede gewünschte Semantik zu implementieren (z. B. sogenannte Standardargumentwerte). Endnote]

Die [Note] ist am relevantesten. Wenn Sie Reflection verwenden, spielen Sie die Rolle des Compilers und es liegt an Ihnen, den Standardwert korrekt zu interpretieren. Mit anderen Worten, Sie müssen wissen, dass in C# der Standardwert für einen Strukturtyp null ist.

Dies ist ansonsten eine Einschränkung dessen, was in einem [FieldInit] gespeichert werden kann. Der Initialisierungswert muss in den Metadaten der Assembly gespeichert werden und unterliegt den gleichen Einschränkungen einer [Attribut] -Deklaration. Es können nur einfache Werttypen verwendet werden, keine Strukturen. So per Konvention die C# -Designer arbeiteten um dies mit der Verwendung von null stattdessen. Gut genug für den C# -Compiler zu verstehen, dass default(Guid) beabsichtigt war.

Das ist nicht der Punkt, wo die Probleme enden, es gibt andere Möglichkeiten, ein optionales Argument anzugeben, das [OptionalAttribute] wurde vor C# v4.0 verwendet. Und wird heute noch von COM-Interop-Bibliotheken und anderen Sprachen wie VB.NET und C++/CLI verwendet. Dort leben die Drachen.

diese Tiere Ignorieren jetzt, Abhilfe Code für das, was Sie haben könnte wie folgt aussehen:

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); 
+0

Sollen wir sagen, dass alles, was kein "const" sein kann, auf null ausgewertet wird? Also werden nur 'einfache Typen',' enum types' und 'string' nicht auf null ausgewertet? (mit einem speziellen Fall für 'default (strting)' als String ist ein ref-Typ, als ist natürlich null) –

+0

Das ruft nur einen anderen Drachen. Sie können kein * const Guid * deklarieren, aber wie Sie sehen können, funktioniert es als Standardwert. Also nein, das kannst du nicht sagen. –

1

Sorry - ich kann Ihnen nicht sagen, warum es nicht den richtigen Wert zurückgibt, aber ich habe eine workarround diese Erweiterung Methode:

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

Vielleicht wird es helfen.

+0

Thx für die Antwort. Bedauerlicherweise interessiere ich mich mehr für das WARUM. Ich habe meine Frage aktualisiert und erklärt, wie ich hierher gekommen bin. Danke nochmal. –

2

Öffnen Sie die Testdatei mit einem Decompiler Ihrer Wahl und Sie werden sehen, dass der Code tatsächlich kompiliert erhalten:

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

Aus diesem Grund ist die Eigenschaft Standardwert null zurückgibt. Wenn Sie diesen Parameter jetzt verwenden, können Sie sagen hinzufügen:

Console.WriteLine(id); 

Der Compiler fügt einen

box [mscorlib]System.Guid 

Erklärung vor dem Console.WriteLine. Jetzt ist die Ausgabe die erwartete "0000000 -..."

1

Ich denke, die Antwort kann in C# -Sprache Spezifikationen gefunden werden (jemand etwas mehr relevant finden kann)

Punkt 4.1.2

Wenn die Operanden eines Ausdrucks alle einfachen Typ Konstanten sind, ist es möglich ist, dass der Compiler den Ausdruck auszuwerten an Kompilierzeit. Ein solcher Ausdruck wird als Konstantenausdruck (§7.19) bezeichnet. Ausdrücke, die Operatoren enthalten, die von anderen Strukturtypen definiert sind, werden nicht als konstante Ausdrücke betrachtet.

Durch const-Deklarationen ist es möglich, Konstanten der einfachen Typen zu deklarieren (§10.4). Es ist nicht möglich, Konstanten anderer Struct-Typen zu haben, aber ein ähnlicher Effekt wird durch statische Readonly Felder zur Verfügung gestellt.

Auch wenn wir reden hier nicht über Ausdrücke oder consts hier, so scheint es, dass nur einfache Typen (Strukturen wie int) ausgewertet werden können/bei der Kompilierung berechnet. Aus diesem Grund kann default(int) ausgewertet werden und dann in Ihrem Code 0 angezeigt werden.

Aber andere Struktur kann nicht zur Kompilierzeit berechnet werden, also würde ich sagen, dass es der Grund ist, warum sie zu null ausgewertet werden.

Wenn Sie einen Standardparameterwert festlegen, muss es sich um eine Kompilierzeitkonstante handeln.

Der schwierige Teil ist, dass Sie default(Guid) oder new Guid() als akzeptierte Kompilierung-Konstante (BUT auszuwertenden null) setzen können, aber ... es ist ein const nicht sein kann.

Zum Beispiel könnten Sie

const Guid g = new Guid(); 
Verwandte Themen