2010-03-03 71 views
5

我正在编写一个运行“东西”的计划。C#中的反射和参数#

想法是数据库包含程序集,方法信息和参数值。计时器将出现,反映要运行的方法,添加参数并执行该方法。

一切都很好,除了参数。

因此,假设该方法接受CustomerType的ENUM,其中CustomerType具有CustomerType.Master和CustomerType.Associate两个值。

编辑 我不知道会中获得通过参数的类型。使用ENUM作为一个例子编辑的 END

我们要运行的方法“X “并传入参数”CustomerType.Master“。在数据库中,将会有一个“CustomerType.Master”的varchar条目。

如何将字符串“CustomerType.Master”转换为类型为“Master”的CustomerType类型?

由于提前,

吉姆

+0

我认为你正在寻找的字'动态'而不是'一般'。当您的需求描述需要从描述性字符串动态创建参数值时,您已经获得了许多有关此用法的解答。我是否正确地理解你? – 2010-03-03 17:15:16

+0

我对所有人回答的其他问题是:在回答之前没有人完全阅读和理解问题吗? Jeez .... – 2010-03-03 17:16:33

+0

好的,下一个问题:你在调用什么方法?调度方法已知的现有实例化对象,还是您正在实例化目标? – 2010-03-03 17:44:45

回答

1

我想你有两个主要选择:

  1. 将类型名称与参数值一起存储,并使用该名称来使用Type.GetType(string)来投射东西来解析相关类型。
  2. 标准化所有要调用的方法来接受一个字符串数组,并期望这些方法可以进行必要的转换。

我知道你已经说过你没有做选项1,但是从调用函数的角度来看它会有所帮助。

如果假设所有值都可以由字符串表示并转换为适当类型,则选项2是处理这种情况的更“通用”方式。当然,只有当你真正控制被调用方法的定义时,这才有所帮助。

+0

谢谢山姆。为选择2而兴奋。不是我想做的事。 当我有机会时,我会多考虑一下。 - 恼人的是我有参数作为一个字符串;我知道我想传入的参数的类型(通过查看数据库或通过反射)。我只是不能转换。 再次感谢。 – BIDeveloper 2010-03-04 15:35:22

2

OK,问题的范围转移,但我原来的观察和反对一些其他的解决方案仍然有效。

我认为你不想在这里使用'泛型'。您不会提前知道类型,因为您需要创建类型,所以不需要使用通用实现,因为MethodBase.Invoke需要一个Object数组。

此代码假定您正在从数据库字段实例化目标。如果不是相应调整。

当然这不是全部包含的,也没有有用的异常处理,但它可以让你动态地执行任意类型的任意方法,任意类型的参数值都来自一行中的字符串值。

注意:有许多许多情况下,这个简单的执行程序将无法正常工作。您需要确保您设计动态方法,以配合您最终决定使用的任何策略。

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

他们可能会使用C#4.0中的新动态类型或切换到另一种语言,因为这不是我想要维护的。 – ChaosPandion 2010-03-03 18:26:49

+0

嘿,我只是在问题的背景下回答了一个问题。 ;-)。 – 2010-03-03 18:32:56

-1

如果您使用.NET 4,则可以执行以下操作。

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

@Chaos,他没有这个类型。他正在从数据库中的字符串创建参数值.... – 2010-03-03 17:04:02

+0

@Sky - 我重读了这个问题两次,发现没有提及没有类型。 – ChaosPandion 2010-03-03 17:13:18

+0

哈哈。也许第三次会做的伎俩!他正在从数据库行中的字符串构建方法库和参数。我会假设不会有这种类型...... – 2010-03-03 17:21:07

1

下面是我在.NET 3.5中使用的一种有用的扩展方法。

有了这个扩展方法可用,你的代码看起来是这样的:

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

通过提供的参数的默认值,编译器会知道你想要的枚举您的字符串变成。它会尝试在Enum中找到你的文本。如果不是,它将返回默认值。

这里是扩展方法:(!这个版本也做部分匹配,所以即使是“M”将很好地工作)

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

@格伦,正如我对混沌所说的,如果你阅读了问题中提到的要求,他没有类型。事实上,他甚至不知道参数*是否是*和枚举。他只需要能够从字符串构造一个参数值,并且无法弄清楚如何使用枚举来完成。 – 2010-03-03 17:13:31

+0

@格伦,对不起,我低估了你。尽管它没有解决这个问题,但这是一个很好的小班 - 我想不打倒,这实际上导致了最后的投票。 ;-)请编辑你的问题,以便我可以这样做... – 2010-03-03 18:31:38

+0

@Sky ...为你完成。感谢您的跟踪。 – 2010-03-09 07:15:27

0

我还没有完全理解你的问题......但是,你说“除了参数,一切都很好。”

我会假设“CustomerType”是您的对象上的属性的名称,“主”是您要放入该属性中的字符串值。

这是(另一种)扩展方法,可能有所帮助。

一旦你有你的新对象,并从数据库字段中的值和属性名,你可以这样做:

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

myNewObject.SetPropertyValue(propertyName, newValue) 

方法:

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