代码之家  ›  专栏  ›  技术社区  ›  BIDeveloper

C中的反射和参数#

  •  5
  • BIDeveloper  · 技术社区  · 15 年前

    我正在编写一个应用程序,它按计划运行“Things”。

    数据库包含程序集、方法信息以及参数值。计时器将出现,反映要运行的方法,添加参数,然后执行该方法。

    除了参数,一切都很好。

    因此,假设该方法接受customertype的枚举,其中customertype有两个值customertype.master和customertype.associate。

    编辑 我不知道要传入的参数类型。用作示例的枚举 编辑结束

    我们要运行方法“x”并传入参数“customertype.master”。在数据库中,将有一个varchar条目“customertype.master”。

    如何将字符串“customertype.master”转换为一般值为“master”的customertype类型?

    事先谢谢,

    吉姆

    5 回复  |  直到 15 年前
        1
  •  1
  •   Sam Erwin    15 年前

    我认为你有两个主要的选择:

    1. 将类型名与参数值一起存储,并使用该值来强制转换 Type.GetType(string) 以解析有问题的类型。
    2. 以这种方式标准化所有要调用的方法以接受字符串数组,并期望这些方法执行任何必要的强制转换。

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

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

        2
  •  2
  •   Sky Sanders    15 年前

    好吧,问题的范围改变了,但我最初的观察和对其他解决方案的反对仍然存在。

    我认为你不/不想在这里使用“仿制药”。您提前不知道类型,并且由于需要创建该类型,因此不需要使用泛型实现,因为methodBase.invoke接受一个对象数组。

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

    当然,这并不是全部包含,也没有有用的异常处理,但是它允许您对任意类型动态执行任意方法,其中任意参数值都来自一行中的字符串值。

    注释 :在许多情况下,这个简单的执行器将无法工作。你需要确保你设计出你的动态方法来配合你最终决定使用的任何策略。

    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);
            }
        }
    
    }
    
        3
  •  1
  •   Glen Little    15 年前

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

    有了这个扩展方法,您的代码可能如下所示:

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

    通过在参数中提供默认值,编译器将知道您希望将字符串转换为哪个枚举。它将尝试在枚举中查找文本。否则,它将返回默认值。

    这里是扩展方法:(这个版本也进行部分匹配,所以即使是“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;
        }
    
        4
  •  0
  •   Glen Little    15 年前

    我还是不完全理解你的问题…但是,您会说“除了参数之外一切都很好。”

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

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

    一旦从“数据库”字段中获得了新对象和值和属性名,就可以使用:

    // 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);
    }
    
        5
  •  -1
  •   ChaosPandion    15 年前

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

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