2010-03-03 14 views
5

Ich schreibe eine Anwendung, die "Dinge" zu einem Zeitplan ausführt.Reflection & Parameters in C#

Die Idee ist, dass die Datenbank Assembly, Methodeninformationen und auch die Parameterwerte enthält. Der Timer wird kommen, die auszuführende Methode widerspiegeln, die Parameter hinzufügen und dann die Methode ausführen.

Alles ist gut, außer für die Parameter.

Nehmen wir an, die Methode akzeptiert ein ENUM von CustomerType, wobei CustomerType zwei Werte von CustomerType.Master und CustomerType.Associate hat.

EDIT Ich weiß nicht, die Art der Parameter, die übergeben werden immer. ENUM als Beispiel verwendet ENDE EDIT

Wir wollen Methode „X laufen "und übergeben Sie den Parameter" CustomerType.Master ". In der Datenbank gibt es einen Varchar-Eintrag von "CustomerType.Master".

Wie konvertiere ich die Zeichenfolge "CustomerType.Master" generisch in einen Typ von CustomerType mit einem Wert von "Master"?

Vielen Dank im Voraus,

Jim

+0

Ich denke, das Wort Sie suchen, ist 'dynamisch' statt 'generisch'. Sie haben mit dieser Verwendung einige knieschwere Antworten erhalten, wenn Ihre Anforderungen die Notwendigkeit beschreiben, einen Parameterwert aus beschreibenden Zeichenfolgen dynamisch zu erstellen. Versteh ich dich richtig? –

+0

Meine andere Frage an alle, die antworten, lautet: Liest und versteht niemand eine Frage vollständig, bevor er antwortet? Jeez .... –

+0

Ok, nächste Frage: Nach was rufen Sie Methoden auf? Ein vorhandenes instanziiertes Objekt, das der Scheduling-Methode bekannt ist, oder Sie instanziieren das Ziel ebenfalls? –

Antwort

1

Ich würde denken, Sie haben zwei wichtige Optionen:

  1. Speichern Sie den Typnamen zusammen mit dem Parameterwert, und verwenden Sie diesen, um Objekte mithilfe von Type.GetType(string) zu konvertieren, um den betreffenden Typ aufzulösen.
  2. Standardisieren Sie alle Methoden, die auf diese Weise aufgerufen werden, um ein Array von Strings zu akzeptieren, und erwarten Sie, dass die Methoden alle erforderlichen Castings ausführen.

Ich weiß, Sie haben gesagt, dass Sie nicht die Option 1 tun, aber es würde Dinge vom Standpunkt der Aufruf der Funktionen helfen.

Option 2 ist die weitaus "generische" Methode, um die Situation zu bewältigen, vorausgesetzt, dass alle Werte durch Strings repräsentiert und von Strings in den entsprechenden Typ konvertiert werden können. Das hilft natürlich nur, wenn Sie tatsächlich die Definition der aufgerufenen Methoden beherrschen.

+0

Danke Sam. Gedreht für Option 2. Nicht was ich machen wollte. Ich werde etwas mehr darüber nachdenken, wenn ich die Chance bekomme. - die nervige Sache ist, ich habe den Parameter als eine Zeichenfolge; Ich kenne den Typ des Parameters, den ich übergeben möchte (entweder in der Datenbank oder durch Reflexion). Ich kann einfach nicht konvertieren. Danke nochmal. – BIDeveloper

2

OK, verlagerte sich der Umfang der Frage, aber meiner ursprünglichen Beobachtung und Einwände gegen einige andere Lösungen steht still.

Ich denke, dass Sie Generika hier nicht verwenden können/wollen. Sie kennen den Typ nicht im Voraus, und da Sie den Typ erstellen müssen, müssen Sie keine generische Implementierung verwenden, da MethodBase.Invoke ein Array von Object verwendet.

In diesem Code wird davon ausgegangen, dass Sie das Ziel aus dem Datenbankfeld instanziieren. Wenn nicht einfach entsprechend anpassen.

Natürlich ist dies nicht alles umfassend und hat keine nützliche Ausnahmebehandlung, aber es erlaubt Ihnen, beliebige Methoden auf einem beliebigen Typ mit beliebigen Parametern dynamisch auszuführen, die alle aus String-Werten in einer Zeile kommen.

HINWEIS: Es gibt viele viele Szenarien, in denen dieser einfache Executor nicht funktioniert. Sie müssen sicherstellen, dass Sie Ihre dynamischen Methoden so entwickeln, dass sie mit der Strategie, die Sie letztendlich verwenden, zusammenarbeiten.

using System; 
using System.ComponentModel; 
using System.Drawing; 
using System.Globalization; 
using System.Reflection; 
using NUnit.Framework; 

namespace DynamicMethodInvocation 
{ 

    [TestFixture] 
    public class Tests 
    { 
     [Test] 
     public void Test() 
     { 
      // from your database 
      string assemblyQualifiedTypeName = "DynamicMethodInvocation.TestType, DynamicMethodInvocation"; 
      string methodName = "DoSomething"; 

      // this is how you would get the strings to put in your database 
      string enumString = Executor.ConvertToString(typeof(AttributeTargets), AttributeTargets.Assembly); 
      string colorString = Executor.ConvertToString(typeof(Color), Color.Red); 
      string stringString = "Hmm... String?"; 

      object result = Executor.ExecuteMethod(assemblyQualifiedTypeName, methodName, 
                new[] { enumString, colorString, stringString }); 

      Assert.IsInstanceOf<bool>(result); 
      Assert.IsTrue((bool)result); 
     } 
    } 


    public class TestType 
    { 
     public bool DoSomething(AttributeTargets @enum, Color color, string @string) 
     { 
      return true; 
     } 
    } 

    public class Executor 
    { 
     public static object ExecuteMethod(string assemblyQualifiedTypeName, string methodName, 
              string[] parameterValueStrings) 
     { 
      Type targetType = Type.GetType(assemblyQualifiedTypeName); 
      MethodBase method = targetType.GetMethod(methodName); 

      ParameterInfo[] pInfo = method.GetParameters(); 
      var parameterValues = new object[parameterValueStrings.Length]; 

      for (int i = 0; i < pInfo.Length; i++) 
      { 
       parameterValues[i] = ConvertFromString(pInfo[i].ParameterType, parameterValueStrings[i]); 
      } 

      // assumes you are instantiating the target from db and that it has a parameterless constructor 
      // otherwise, if the target is already known to you and instantiated, just use it... 

      return method.Invoke(Activator.CreateInstance(targetType), parameterValues); 
     } 


     public static string ConvertToString(Type type, object val) 
     { 
      if (val is string) 
      { 
       return (string) val; 
      } 
      TypeConverter tc = TypeDescriptor.GetConverter(type); 
      if (tc == null) 
      { 
       throw new Exception(type.Name + " is not convertable to string"); 
      } 
      return tc.ConvertToString(null, CultureInfo.InvariantCulture, val); 
     } 

     public static object ConvertFromString(Type type, string val) 
     { 
      TypeConverter tc = TypeDescriptor.GetConverter(type); 
      if (tc == null) 
      { 
       throw new Exception(type.Name + " is not convertable."); 
      } 
      if (!tc.IsValid(val)) 
      { 
       throw new Exception(type.Name + " is not convertable from " + val); 
      } 

      return tc.ConvertFrom(null, CultureInfo.InvariantCulture, val); 
     } 
    } 

} 
+0

Sie können auch den neuen dynamischen Typ aus C# 4.0 verwenden oder zu einer anderen Sprache wechseln, da dies nicht gewünscht ist. – ChaosPandion

+0

Hey, ich beantworte nur eine Frage in dem Kontext, in dem es gefragt wurde. ;-). –

-1

Wenn Sie .NET 4 verwenden, können Sie Folgendes tun.

var result = default(CustomerType); 
if (!Enum.TryParse("Master", out result)) 
{ 
    // handle error 
} 
+0

@Chaos, er hat nicht den Typ. Er erstellt den Parameterwert aus Strings in der Datenbank .... –

+0

@Sky - Ich habe die Frage zweimal gelesen und keine Erwähnung gefunden, dass ich diesen Typ nicht habe. – ChaosPandion

+0

lol. vielleicht wird das dritte Mal den Trick machen! Er erstellt eine Methodebase und Parameter aus Strings in einer Datenbankzeile. Ich würde davon ausgehen, dass der Typ nicht abgeleitet werden würde ... –

1

Unten ist eine nützliche Erweiterungsmethode, die ich in .NET 3.5 verwende.

Mit dieser Erweiterung Methode zur Verfügung, der Code wie folgt aussehen könnte:

var valueInDb = GetStringFromDb().Replace("CustomerType.", string.Empty); 
var value = valueInDb.ToEnum(CustomerType.Associate); 

Durch die Versorgung des Standardwert in dem Parameter, wird der Compiler wissen, welche Enum Sie Ihre Zeichenfolge wollen in zu drehen. Es wird versuchen, Ihren Text in der Enum zu finden. Wenn dies nicht der Fall ist, wird der Standardwert zurückgegeben.Hier

ist die Erweiterung Methode: (! Diese Version auch teilweise Übereinstimmungen der Fall ist, so auch "M" würde gut funktionieren)

public static T ToEnum<T>(this string input, T defaultValue) 
    { 
     var enumType = typeof (T); 
     if (!enumType.IsEnum) 
     { 
     throw new ArgumentException(enumType + " is not an enumeration."); 
     } 

     // abort if no value given 
     if (string.IsNullOrEmpty(input)) 
     { 
     return defaultValue; 
     } 

     // see if the text is valid for this enumeration (case sensitive) 
     var names = Enum.GetNames(enumType); 

     if (Array.IndexOf(names, input) != -1) 
     { 
     // case insensitive... 
     return (T) Enum.Parse(enumType, input, true); 
     } 

     // do partial matching... 
     var match = names.Where(name => name.StartsWith(input, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); 
     if(match != null) 
     { 
     return (T) Enum.Parse(enumType, match); 
     } 

     // didn't find one 
     return defaultValue; 
    } 
+0

@Glen, wie ich zu Chaos gesagt habe, wenn man die in der Frage beschriebenen Anforderungen liest, hat er keinen Type. In der Tat, er weiß nicht einmal, ob der Parameter * ist * und enum.Er muss nur in der Lage sein, einen Parameterwert aus Strings zu konstruieren und konnte nicht herausfinden, wie dies mit Enums geschehen soll. –

+0

@Glen, sorry, ich habe dich abgelehnt. Auch wenn es die Frage nicht anspricht, ist dies eine nette kleine Klasse - ich würde gerne einen Downvote machen, was tatsächlich zu einem Up-Vote führt. ;-) Bitte editiere deine Frage, damit ich das tun kann ... –

+0

@Sky ... ist für dich erledigt. Danke fürs Nachfassen. –

0

Ich verstehe Ihre Frage immer noch nicht ganz ... aber Sie sagen "Alles ist gut, außer für die Parameter."

Ich nehme "CustomerType" den Namen einer Eigenschaft für Ihr Objekt, und "Master" ist der Zeichenfolgenwert, den Sie in diese Eigenschaft einfügen möchten.

Hier ist (eine andere) Erweiterungsmethode, die helfen kann.

Sobald Sie Ihr neues Objekt und den Wert und die Eigenschaftsnamen aus dem Datenbankfeld haben, können Sie diese verwenden:

// string newValue = "Master"; 
// string propertyName = "CustomerType"; 

myNewObject.SetPropertyValue(propertyName, newValue) 

Methode:

/// <summary>Set the value of this property, as an object.</summary> 
public static void SetPropertyValue(this object obj, 
            string propertyName, 
            object objValue) 
{ 
    const BindingFlags attr = BindingFlags.Public | BindingFlags.Instance; 
    var type = obj.GetType(); 

    var property = type.GetProperty(propertyName, attr); 
    if(property == null) return; 

    var propertyType = property.PropertyType; 
    if (propertyType.IsValueType && objValue == null) 
    { 
    // This works for most value types, but not custom ones 
    objValue = 0; 
    } 

    // need to change some types... e.g. value may come in as a string... 
    var realValue = Convert.ChangeType(objValue, propertyType); 

    property.SetValue(obj, realValue, null); 
}