从bindinglist获取并设置值<;T>;坐标为c#

从bindinglist获取并设置值<;T>;坐标为c#,c#,extension-methods,bindinglist,C#,Extension Methods,Bindinglist,当类型发生变化时,我无法获取和设置绑定列表中带有坐标的项的值 例如,假设我有三门课: 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 Ow

当类型发生变化时,我无法获取和设置绑定列表中带有坐标的项的值

例如,假设我有三门课:

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; }
}
然后是三个绑定列表(假设它们已填充):

例如,我需要能够设置listThree中项目(2,3)的值,以及listThree中项目(1,1)的值:

listThree.SetValueByCoordinates(2,3,false);
listThree.SetValueByCoordinates(1,1,"My self");
或者从列表一和列表二中获取值(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*/ }
public静态对象GetValueByCoordinates(这个BindingList列表,int x,int y){/*some magic*/}

任何帮助都将不胜感激。

如前所述,我非常怀疑您寻求帮助的方法是否可能是解决您试图解决的更广泛问题的最佳或最合适的方法。这是可以做到的(而且没有太多困难),但是结果代码很难维护,容易出错,可读性也不强(这导致了前两个问题)

也就是说,有很多不同的方法来实现您所要求的特定行为。即使这不是解决当前问题的最佳方法,但了解其他类型问题的基本技巧也很有用。考虑到这一点,这里有两种最明显的解决问题的方法


手动配置从索引到getter和setter的映射:

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,
}
我认为这是最好的方法。不是因为它优雅或易于扩展,而是因为它不是这两种东西中的任何一种。要求代码维护人员显式地创建数据结构元素,以支持您要处理的每种类型和属性,这将阻止该技术在其他相关问题上的扩散,甚至在当前问题上的扩散。它甚至可以鼓励人们花更多的时间思考更广泛的问题,以便找到更好的策略

这种方法确实具有其合理性能的优势。因为代码是在编译时生成的,所以唯一真正的开销是值类型的装箱。有一些强制转换,但对于引用类型,开销实际上是不可测量的,甚至装箱开销也可能不会显示在概要文件上,这取决于此代码的使用强度

此特定解决方案如下所示:

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,
                }
            },
        };
}
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();
    }
}
在此版本中,代码检索给定类型的
PropertyInfo
对象数组,按名称对该数组排序,检索给定索引的相应
PropertyInfo
对象,然后使用该
PropertyInfo
对象设置或获取属性值(视情况而定)

反射会带来巨大的运行时性能开销。这个特定的实现通过缓存
PropertyInfo
对象的排序数组来减轻一些开销。这样,它们只需要创建一次,代码第一次必须处理给定类型的对象


演示代码:

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,
}
正如我所提到的,为了更容易地比较这两种方法,而无需转到每个方法调用并手动更改用于调用的整数文本,我创建了一些简单的
enum
类型来表示属性索引。我还编写了一些代码来初始化一些可以测试的列表

注意:需要指出的一件非常重要的事情是,在您的问题中,您对如何为属性编制索引不是很一致。在我的代码示例中,我选择使用基于0的索引(与C#数组和其他集合中使用的自然索引一致)。当然,您可以使用不同的基(例如,基于1的索引),但您需要确保在整个代码中完全一致(包括在实际索引数组时从传入的索引中减去1)

我的演示代码如下所示:

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,
                }
            },
        };
}
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();
    }
}
最后,这里是使用的
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,
}
反射/按名称排序:

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,这样相同的索引就可以以任何方式工作。你决定做什么并不重要;它必须是一致的


最后的想法…

我有没有提到过我会多么强烈地建议不要围绕这项技术构建大量代码?目前还不清楚您试图解决的实际更大的问题是什么,但有很多不同的方法会导致出现错误,并且很可能会导致大量难以发现的问题,需要花费大量时间来修复代码中的错误


就性能而言,只要您没有在大量对象和属性值的紧密循环中执行代码,上述情况就不会太糟糕。特别是手动(第一个)示例应该相对较快。通过使用
表达式
类型,可以以手动方法的最小开销实现基于反射方法的通用设计。这有点复杂,但有一个优点,就是您可以动态生成表达式,从而有效地实现手动方法的编译代码。

如前所述,我非常怀疑你寻求帮助的方法可能是解决你试图解决的更广泛问题的最佳或最合适的方法。这是可以做到的