2010-03-03 65 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); 
}