2015-04-12 29 views
0

我無法在類型變化時使用座標獲取和設置綁定列表中項目的值。從座標列表中獲取並設置綁定列表<T>的值c#

例如,假設我有三類:

public class Client{ 
    public string Name{ get; set; } 
} 

public class Debt{ 
    public string AccountType{ get; set; } 
    public int DebtValue { get; set; } 
} 

public class Accounts{ 
    public string Owner{ get; set; } 
    public int AccountNumber { get; set; } 
    public bool IsChekingAccount { get; set; } 
} 

,然後,三個bindinglists(想象一下它們填充):

public BindingList<Client> listOne; 
public BindingList<Debt> listTwo; 
public BindingList<Accounts> listThree; 

我試圖創建一個擴展方法用所請求的值返回Object,或者在提供值的情況下設置該值。

public static Object GetValueByCoordinates(this IBindingList list, int x, int y) { /*some magic*/ } 

public static Object SetValueByCoordinates(this IBindingList list, int x, int y, Object value) { /*some other magic*/ } 

所以,舉例來說,我需要能夠設置在listThree項目(2,3)的listTwo值,和值(1,1):

listThree.SetValueByCoordinates(2,3,false); 
listThree.SetValueByCoordinates(1,1,"My self"); 

或得到那麼listOne和listTwo值(1,1)和(2,2):

string result = listOne.GetValueByCoordinates(1,1).ToString(); 
intresult = Convert.ToInt32(listOne.GetValueByCoordinates(1,1)); 

你將如何實現這樣的行爲?我正在考慮使用反射,但我對此一無所知。

請注意方法必須利用這樣這樣的事情一定要避免

public static Object GetValueByCoordinates<T>(this BindingList<T> list, int x, int y) { /*some magic*/ } 

任何幫助將不勝感激被稱爲是這樣。

+0

請更具體地解釋「座標」的含義。它幾乎看起來像你想在一個類中編號屬性,並使用該索引作爲'y'座標(「x」座標是列表中的索引?)。但問題一點都不清楚。另外,爲什麼輸入參數必須是'IBindingList'?爲什麼不實施'IList'?最後,這個問題聞起來像一個[XY問題](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)。你可能會考慮解釋更廣泛的目標是什麼,因爲這種特定的方法看起來很r。。 –

+0

正如你所說,座標的想法是使用「Y」來獲取在「Y」位置聲明的對象的屬性,並將「X」作爲列表中項目的索引。 – Zar

+0

另外,輸入是一個IBindingList,因爲應用程序是用這個編寫的(我猜這是因爲它們被用作網格的數據源)。 儘管在某種程度上是一個XY問題,但我的問題是要知道BindingList中使用的對象的類型,並在不知道名稱的情況下獲取/設置其屬性。 – Zar

回答

4

如前所述,我非常懷疑您要求幫助的方法可能是解決您嘗試解決的更廣泛問題的最佳或最合適的方法。它可以完成(並且沒有太多困難),但是由此產生的代碼難以維護,容易出錯,並且不易讀(導致前兩個問題)。

也就是說,有很多不同的方法來實現你所要求的特定行爲。即使這不是解決當前問題的最佳方法,基本技術對於瞭解其他類型的問題也很有用。考慮到這一點,這裏有兩個最明顯的方法可以解決您的問題。


手動配置從索引getter和setter的映射:

IMHO這是最優選的方式。不是因爲它的優雅或易於延伸,而是因爲它的而不是其中之一。要求代碼維護者明確地創建數據結構元素來支持你想要處理的每種類型和屬性將會阻礙這種技術對於其他相關問題的擴散,甚至是當前的問題。它甚至可以鼓勵某人花更多時間思考更廣泛的問題,從而找到更好的策略。

這種方法確實具有合理的性能優勢。由於代碼是在編譯時生成的,唯一的實際開銷是值類型發生的裝箱。有一些轉換,但對於引用類型來說,開銷應該幾乎不可測量,甚至拳擊開銷可能不會顯示在配置文件中,具體取決於可能使用的代碼的密集程度。

這種特定的解決方案是這樣的:

static class ManualIndexedProperty 
{ 
    public static void SetValueByCoordinates(this IBindingList list, int x, int y, object value) 
    { 
     object o = list[x]; 

     _typeToSetter[o.GetType()][y](o, value); 
    } 

    public static object GetValueByCoordinates(this IBindingList list, int x, int y) 
    { 
     object o = list[x]; 

     return _typeToGetter[o.GetType()][y](o); 
    } 

    private static readonly Dictionary<Type, Func<object, object>[]> _typeToGetter = 
     new Dictionary<Type, Func<object, object>[]>() 
     { 
      { 
       typeof(Client), 
       new Func<object, object>[] 
       { 
        o => ((Client)o).Name 
       } 
      }, 

      { 
       typeof(Debt), 
       new Func<object, object>[] 
       { 
        o => ((Debt)o).AccountType, 
        o => ((Debt)o).DebtValue, 
       } 
      }, 

      { 
       typeof(Accounts), 
       new Func<object, object>[] 
       { 
        o => ((Accounts)o).Owner, 
        o => ((Accounts)o).AccountNumber, 
        o => ((Accounts)o).IsChekingAccount, 
       } 
      }, 
     }; 

    private static readonly Dictionary<Type, Action<object, object>[]> _typeToSetter = 
     new Dictionary<Type, Action<object, object>[]>() 
     { 
      { 
       typeof(Client), 
       new Action<object, object>[] 
       { 
        (o1, o2) => ((Client)o1).Name = (string)o2 
       } 
      }, 

      { 
       typeof(Debt), 
       new Action<object, object>[] 
       { 
        (o1, o2) => ((Debt)o1).AccountType = (string)o2, 
        (o1, o2) => ((Debt)o1).DebtValue = (int)o2, 
       } 
      }, 

      { 
       typeof(Accounts), 
       new Action<object, object>[] 
       { 
        (o1, o2) => ((Accounts)o1).Owner = (string)o2, 
        (o1, o2) => ((Accounts)o1).AccountNumber = (int)o2, 
        (o1, o2) => ((Accounts)o1).IsChekingAccount = (bool)o2, 
       } 
      }, 
     }; 
} 

兩個庫聲明,分別用於設置和獲取屬性值。字典將元素對象的類型映射到委託實例數組以執行實際工作。每個委託實例引用一個匿名方法,該方法已被手動編碼以執行必要的操作。這種方法

一個主要優點是,它是明確的和明顯對應於每種類型的財產是什麼什麼指標。

這種做法將是繁瑣且耗時的設置,如果你正在處理的類型和/或性質的任何顯著數量。但恕我直言,這是一件好事。正如我上面提到的那樣,希望這種方法的痛苦可以幫助說服某人放棄按索引訪問房產的想法。 :)

如果這種單調乏味的是不可接受的,但你仍然堅持索引屬性的訪問方式,那麼您可以在事實上使用反射來替代&hellip;


使用反射來訪問屬性:

這種技術是更動態。一旦實現,它可以在沒有修改的情況下適用於任何類型的對象,並且不需要額外的工作來支持新類型。

一個主要缺點是爲了產生一致的,可預測的結果,它按名稱排序屬性。這確保了C#編譯器和/或CLR中的更改不會破壞代碼,但這意味着您無法添加或刪除類型的屬性,而無需更新通過索引訪問這些屬性的代碼。

在我的演示使用代碼中(詳見下文),我通過聲明enum類型爲屬性名稱提供int值來解決此維護問題。如果代碼實際上是指具有文字索引值的屬性,這將是一種很好的方法來幫助減少維護開銷。

然而,有可能您的方案包括通過指數,例如動態訪問屬性值在序列化場景或類似情況下。在這種情況下,如果將屬性添加或刪除到類型中,您還需要添加可重新映射或以其他方式處理索引值更改的內容。

坦率地說,無論哪種方式的索引類型改變的這個問題是一個很大的原因我強烈建議對這個索引訪問性擺在首位。但是,如果你堅持......

static class ReflectionIndexedProperty 
{ 
    public static void SetValueByCoordinates(this IBindingList list, int x, int y, object value) 
    { 
     object o = list[x]; 

     GetProperty(o, y).SetValue(o, value); 
    } 

    public static object GetValueByCoordinates(this IBindingList list, int x, int y) 
    { 
     object o = list[x]; 

     return GetProperty(o, y).GetValue(o); 
    } 

    private static PropertyInfo GetProperty(object o, int index) 
    { 
     Type type = o.GetType(); 
     PropertyInfo[] properties; 

     if (!_typeToProperty.TryGetValue(type, out properties)) 
     { 
      properties = type.GetProperties(); 
      Array.Sort(properties, (p1, p2) => string.Compare(p1.Name, p2.Name, StringComparison.OrdinalIgnoreCase)); 
      _typeToProperty[type] = properties; 
     } 

     return properties[index]; 
    } 

    private static readonly Dictionary<Type, PropertyInfo[]> _typeToProperty = new Dictionary<Type, PropertyInfo[]>(); 
} 

在這個版本中,代碼檢索PropertyInfo對象對於給定類型的陣列中,通過名字排序該陣列,檢索對於給定的索引相應的PropertyInfo對象,然後使用該PropertyInfo對象來設置或者獲得物業價值,酌情。

反思招致顯著運行時的性能開銷。通過緩存PropertyInfo對象的排序陣列,該特定實施減輕了該開銷的一些的一些。這樣,它們只需要創建一次,代碼首次需要處理給定類型的對象。


演示代碼:

正如我所提到的,以使其更容易,而不必去到每一個方法調用和兩種方法比較手工更改文字用於呼叫的整數,我創建一些簡單的enum類型來表示屬性索引。我還寫了一些代碼來初始化一些可以測試的列表。

注意:需要指出的一點非常重要的一點是,在你的問題中,你對於如何索引屬性並不十分一致。在我的代碼示例中,我選擇了使用基於0的索引(與C#數組和其他集合中使用的自然索引一致)。您當然可以使用不同的基礎(例如基於1的索引),但是您需要確保在整個代碼中完全一致(包括在實際編制索引數組時,從傳入索引中減去1)。

我的演示代碼如下所示:

class Program 
{ 
    static void Main(string[] args) 
    { 
     BindingList<Client> listOne = new BindingList<Client>() 
     { 
      new Client { Name = "ClientName1" }, 
      new Client { Name = "ClientName2" }, 
      new Client { Name = "ClientName3" }, 
     }; 

     BindingList<Debt> listTwo = new BindingList<Debt>() 
     { 
      new Debt { AccountType = "AccountType1", DebtValue = 29 }, 
      new Debt { AccountType = "AccountType2", DebtValue = 31 }, 
      new Debt { AccountType = "AccountType3", DebtValue = 37 }, 
     }; 

     BindingList<Accounts> listThree = new BindingList<Accounts>() 
     { 
      new Accounts { Owner = "Owner1", AccountNumber = 17, IsChekingAccount = false }, 
      new Accounts { Owner = "Owner2", AccountNumber = 19, IsChekingAccount = true }, 
      new Accounts { Owner = "Owner3", AccountNumber = 23, IsChekingAccount = true }, 
     }; 

     LogList(listThree); 

     listThree.SetValueByCoordinates(2, (int)AccountsProperty.IsChekingAccount, false); 
     listThree.SetValueByCoordinates(1, (int)AccountsProperty.Owner, "My self"); 

     LogList(listThree); 

     string result1 = (string)listOne.GetValueByCoordinates(0, (int)ClientProperty.Name); 
     int result2 = (int)listTwo.GetValueByCoordinates(1, (int)DebtProperty.DebtValue); 

     LogList(listOne); 
     LogList(listTwo); 

     Console.WriteLine("result1: " + result1); 
     Console.WriteLine("result2: " + result2); 
    } 

    static void LogList<T>(BindingList<T> list) 
    { 
     foreach (T t in list) 
     { 
      Console.WriteLine(t); 
     } 

     Console.WriteLine(); 
    } 
} 

請注意,我用簡單的鑄件從object轉換爲特定的類型,既設置屬性值,讓他們。這是一個比例如呼叫ToString()Convert.ToInt32();您確切知道類型應該是什麼類型,它可以是該類型的實際實例(對於引用類型),也可以是裝箱實例(對於值類型),並且無論哪種方式,演員都可以完全按照您的需要進行操作。

我還添加了ToString()覆蓋到您的示例類,使其更容易看到的輸出:

public class Client 
{ 
    public string Name { get; set; } 

    public override string ToString() 
    { 
     return "{" + Name + "}"; 
    } 
} 

public class Debt 
{ 
    public string AccountType { get; set; } 
    public int DebtValue { get; set; } 

    public override string ToString() 
    { 
     return "{" + AccountType + ", " + DebtValue + "}"; 
    } 
} 

public class Accounts 
{ 
    public string Owner { get; set; } 
    public int AccountNumber { get; set; } 
    public bool IsChekingAccount { get; set; } 

    public override string ToString() 
    { 
     return "{" + Owner + ", " + AccountNumber + ", " + IsChekingAccount + "}"; 
    } 
} 

最後,這裏使用的enum聲明:

手冊索引:

enum ClientProperty 
{ 
    Name = 0 
} 

enum DebtProperty 
{ 
    AccountType = 0, 
    DebtValue = 1 
} 

enum AccountsProperty 
{ 
    Owner = 0, 
    AccountNumber = 1, 
    IsChekingAccount = 2, 
} 

反射/按名稱排序:

enum ClientProperty 
{ 
    Name = 0 
} 

enum DebtProperty 
{ 
    AccountType = 0, 
    DebtValue = 1 
} 

enum AccountsProperty 
{ 
    AccountNumber = 0, 
    IsChekingAccount = 1, 
    Owner = 2, 
} 

當然,這些可能都是相同的值。也就是說,雖然您無法控制排序順序,但一旦給出屬性名稱,手動版本就可以按名稱順序聲明手動編寫的lambda表達式,以便相同的索引可以以任何方式工作。你決定做什麼並不重要;它只需要一致。


最後的想法&hellip;

我有沒有提到我有多強烈建議不要圍繞這種技術構建任何大量的代碼?目前還不完全清楚自己想要解決的實際問題是什麼,但是有很多不同的方法會出現這種問題,並且很可能導致很多難以找到的問題,耗時修復代碼中的錯誤。

就性能而言,只要您沒有在大量對象和屬性值的緊密循環中執行代碼,上面應該不會太糟糕。手動(第一個)例子尤其應該相對較快。通過使用Expression類型,可以通過手動方法的最小開銷實現基於反射的方法的廣義設計。這有點複雜,但它的優點是可以動態生成表達式,從而有效地實現手動方法的編譯代碼實現。

+0

我知道並同意這是一種醜陋的方式來獲取或設置綁定列表中的值,但遺憾的是我們現在被迫這樣做,我非常肯定,將來我們會改變這一點。我正在使用第一種解決方案,目前工作狀況良好(我必須設置相當多的屬性)。謝謝你的幫助 – Zar

+0

Greta回答@Peter!我想分享另一種(也許更自然)的方法;通過[ICustomTypeDescriptor](https://msdn.microsoft.com/en-us/library/system.componentmodel.icustomtypedescriptor%28v=vs.110%29.aspx)接口限制擴展方法。因此,getter可以是公共靜態對象GetValueByCoordinates (這個IEnumerable source,int x,int y)其中TSource:ICustomTypeDescriptor和返回將是'return((ICustomTypeDescriptor)source.ElementAt(x))。GetProperties )[Y];'。 –