2010-03-03 16 views
5

Estoy escribiendo una aplicación que ejecuta "cosas" según un cronograma.Reflection & Parameters en C#

La idea es que la base de datos contiene el ensamblaje, la información del método y también los valores de los parámetros. El temporizador aparecerá, reflejará el método que se ejecutará, agregará los parámetros y luego ejecutará el método.

Todo está bien excepto por los parámetros.

Entonces, digamos que el método acepta un ENUM de CustomerType donde CustomerType tiene dos valores de CustomerType.Master y CustomerType.Associate.

EDITAR No sé el tipo de parámetro que se va a obtener aprobada en. ENUM utiliza como ejemplo FIN DE EDITAR

Queremos ejecutar el método "X "y pase el parámetro" CustomerType.Master ". En la base de datos, habrá una entrada varchar de "CustomerType.Master".

¿Cómo convierto la cadena "CustomerType.Master" en un tipo de CustomerType con un valor de "Master" genéricamente?

Gracias de antemano,

Jim

+0

Creo que la palabra que busca es 'dinámicamente' en lugar de 'genéricamente'. Ha obtenido bastantes respuestas de kneejerk con ese uso cuando sus requisitos describen la necesidad de crear dinámicamente un valor de parámetro a partir de cadenas descriptivas. ¿Te entiendo bien? –

+0

Mi otra pregunta para todos es: ¿Alguna vez alguien leyó y entendió completamente una pregunta antes de responder? Por Dios .... –

+0

Ok, siguiente pregunta: ¿Sobre qué estás invocando métodos? ¿Un objeto instanciado existente que se conoce por el método de programación, o está instantando el objetivo también? –

Respuesta

1

yo creo que tiene 2 opciones principales:

  1. Almacene el nombre del tipo junto con el valor del parámetro y úselo para lanzar cosas usando Type.GetType(string) para resolver el tipo en cuestión.
  2. Estandarice todos los métodos que se llamarán de esta manera para aceptar una matriz de cadenas y espere que los métodos hagan cualquier conversión necesaria.

Sé que ha declarado que no está haciendo la opción 1, pero ayudaría desde el punto de vista de llamar a las funciones.

La opción 2 es la forma más "genérica" ​​de manejar la situación, suponiendo que todos los valores se pueden representar y convertir/convertir de cadenas al tipo apropiado. Por supuesto, eso solo ayuda si tienes control sobre la definición de los métodos que se invocan.

+0

Gracias Sam. Plumped para la opción 2. No es lo que quería hacer. Voy a pensarlo un poco más cuando tenga la oportunidad. - lo molesto es que tengo el parámetro como una cadena; Sé el tipo de parámetro que quiero pasar (ya sea mirando en el DB o por reflexión). Simplemente no puedo convertir. Gracias de nuevo. – BIDeveloper

2

bien, el alcance de la cuestión cambió pero mi observación original y oposición de algunas otras soluciones sigue en pie.

Creo que no puede/no quiere usar 'genéricos' aquí. No conoce el tipo con anticipación, y como deberá crear el tipo, no es necesario utilizar una implementación genérica porque MethodBase.Invoke toma una matriz de Object.

Este código asume que está instanciando el destino desde el campo de la base de datos. Si no solo ajuste en consecuencia.

Por supuesto, esto no abarca todo y no tiene un manejo de excepciones útil, pero le permitirá ejecutar dinámicamente métodos arbitrarios en un tipo arbitrario con valores de parámetros arbitrarios, todos provenientes de valores de cadena en una fila.

NOTA: hay muchos muchos escenarios en los que este simple ejecutor no funcionará. Tendrá que asegurarse de diseñar sus métodos dinámicos para cooperar con cualquier estrategia que termine decidiendo usar.

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

También podrían usar el nuevo tipo dinámico de C# 4.0 o cambiar a otro idioma porque esto no es algo que alguna vez quisiera mantener. – ChaosPandion

+0

Hola, solo estoy respondiendo una pregunta en el contexto en el que se me preguntó. ;-). –

-1

Si está utilizando .NET 4 puede hacer lo siguiente.

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

@Chaos, él no tiene el tipo. Está creando el valor del parámetro a partir de cadenas en la base de datos .... –

+0

@Sky - Releí la pregunta dos veces y no encontré ninguna mención de no tener el tipo. – ChaosPandion

+0

lol. ¡quizás la tercera vez haga el truco! Está construyendo una base de método y parámetros a partir de cadenas en una fila de base de datos. Asumiría que no se deduciría el tipo de ... –

1

A continuación se muestra un método de extensión útil que utilizo en .NET 3.5.

Con este método de extensión disponible, el código podría tener este aspecto:

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

Al suministrar el valor predeterminado en el parámetro, el compilador sabrá qué Enum desea que su cadena se convirtió en. Tratará de encontrar tu texto en el Enum. Si no lo hace, devolverá el valor predeterminado.

Aquí es el método de extensión: (esta versión también hace búsquedas parciales, por lo que incluso "M" que funcionaría muy bien)

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, como le dije a Chaos, si lee los requisitos descritos en la pregunta, no tiene un Tipo. De hecho, él ni siquiera sabe si el parámetro * is * y enum.Solo necesita poder construir un valor de parámetro a partir de cadenas y no pudo encontrar la manera de hacerlo con enumeraciones. –

+0

@Glen, lo siento, le he votado negativamente. A pesar de que no aborda la cuestión, esta es una pequeña clase agradable, me gustaría desairar, lo que en realidad resulta en un voto positivo. ;-) por favor edite su pregunta para que yo pueda hacerlo ... –

+0

@Sky ... está hecho para usted. Gracias por darme seguimiento. –

0

Todavía no entiendo completamente su pregunta ... sin embargo, usted dice "Todo está bien excepto por los parámetros".

Supongo que "CustomerType" es el nombre de una propiedad en su objeto, y "Master" es el valor de cadena que desea colocar en esa propiedad.

Aquí hay (otro) método de extensión que puede ayudar.

vez que tenga su nuevo objeto y el valor y la propiedad de nombre del campo de base de datos, se puede usar esto:

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

myNewObject.SetPropertyValue(propertyName, newValue) 

Método:

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