c# - 使用反射在嵌套对象中设置属性

标签 c# .net-core

我正在尝试使用反射在 obj1 中设置 Address1,但我不知道如何获取对正确对象的引用。我不确定如何获取对 Address1 实例的引用以传递到 SetValue() 的第一个参数

第一轮:

public class StackOverflowReflectionTest
    {
        [Fact]
        public void SetDeepPropertyUsingReflection()
        {
            var breadCrumb = ".Addresses[0].Address1";

            var obj1 = new Person()
            {
                Name = "Eric",
                Addresses = new List<Address>()
                {
                    new Address() {Address1 = "123 First Street"}
                }
            };

            var newAddress1 = "123 Second Street";

            var propNames = breadCrumb.Split(".");
            for (var index = 0; index < propNames.Length; index++)
            {
                var propName = propNames[index];
                if (propName.Contains("["))
                {
                    var propNameToGet = propName.Substring(0, propName.IndexOf("[", StringComparison.Ordinal));
                    var prop = obj1.GetType().GetProperty(propNameToGet);
                    var leftBrace = propName.IndexOf("[", StringComparison.Ordinal);
                    var rightBrace = propName.IndexOf("]", StringComparison.Ordinal);
                    var position = int.Parse(propName.Substring(leftBrace + 1, rightBrace - leftBrace - 1));

                    var propNameToSet = propNames[index + 1];


                    var propToSet = prop.PropertyType.GetGenericArguments()[position].GetProperty(propNameToSet);
                    propToSet.SetValue(obj1, newAddress1);
                }
                else
                {
                    //TODO: deal with different types
                }
            }
        }

        public class Person
        {
            public string Name { get; set; }
            public IList<Address> Addresses { get; set; }
        }

        public class Address
        {
           public string Address1 { get; set; }
        }
    }

Round 2 根据Ed的反馈,仍然停留在如何获取这一行的值: var value = property.GetValue(obj, new object[] { indexPart });

  public class StackOverflowReflectionTest
    {
        [Fact]
        public void SetDeepPropertyUsingReflectionRound2()
        {
            var breadCrumb = "Addresses[0].Address1";

            var obj1 = new Person()
            {
                Name = "Eric",
                Addresses = new List<Address>()
                {
                    new Address() {Address1 = "123 First Street"}
                }
            };

            var newAddress1 = "123 Second Street";

            SetPropertyValueByPath(obj1, breadCrumb, newAddress1);
        }

        public bool CrackPropertyName(string name, out string namePart, out object indexPart)
        {
            if (name.Contains("["))
            {
                namePart = name.Substring(0, name.IndexOf("[", StringComparison.Ordinal));

                var leftBrace = name.IndexOf("[", StringComparison.Ordinal);
                var rightBrace = name.IndexOf("]", StringComparison.Ordinal);

                indexPart = name.Substring(leftBrace + 1, rightBrace - leftBrace - 1);
                return true;
            }
            else
            {
                namePart = name;
                indexPart = null;
                return false;
            }
        }

        public object GetPropertyValue(object obj, string name)
        {
            if(CrackPropertyName(name, out var namePart, out var indexPart))
            {
                var property = obj.GetType().GetProperty(namePart);
                var value = property.GetValue(obj, new object[] { indexPart });
                return value;
            }
            else
            {
                return obj.GetType().GetProperty(name);
            }

        }

        public void SetPropertyValue(object obj, string name, object newValue)
        {
            var property = typeof(Address).GetProperty(name);
            property.SetValue(obj, newValue);
        }

        public void SetPropertyValueByPath(object obj, string path, object newValue)
        {
            var pathSegments = path.Split(".");

            if (pathSegments.Length == 1)
            {
                SetPropertyValue(obj, pathSegments[0], newValue);
            }
            else
            {
                ////  If more than one remaining segment, recurse

                var child = GetPropertyValue(obj, pathSegments[0]);

                SetPropertyValueByPath(child, String.Join(".", pathSegments.Skip(1)), newValue);
            }
        }

        public class Person
        {
            public string Name { get; set; }
            public IList<Address> Addresses { get; set; }
        }

        public class Address
        {
           public string Address1 { get; set; }
        }
}

解决方案:

public class StackOverflowReflectionTest
    {
[Fact]
        public void SetDeepPropertyUsingReflectionSolution()
        {
            var breadCrumb = "Addresses[0].Address1";

            var obj1 = new Person()
            {
                Name = "Eric",
                Addresses = new List<Address>()
                {
                    new Address() {Address1 = "123 First Street"}
                }
            };

            var newAddress1 = "123 Second Street";

            SetPropertyValueByPath(obj1, breadCrumb, newAddress1);
        }

        public bool CrackPropertyName(string name, out string namePart, out object indexPart)
        {
            if (name.Contains("["))
            {
                namePart = name.Substring(0, name.IndexOf("[", StringComparison.Ordinal));

                var leftBrace = name.IndexOf("[", StringComparison.Ordinal);
                var rightBrace = name.IndexOf("]", StringComparison.Ordinal);

                indexPart = name.Substring(leftBrace + 1, rightBrace - leftBrace - 1);
                return true;
            }
            else
            {
                namePart = name;
                indexPart = null;
                return false;
            }
        }

        public object GetPropertyValue(object obj, string name)
        {
            if(CrackPropertyName(name, out var namePart, out var indexPart))
            {

                var property = obj.GetType().GetProperty(namePart);
                var list = property.GetValue(obj);
                var value = list.GetType().GetProperty("Item").GetValue(list, new object[] { int.Parse(indexPart.ToString()) });
                return value;
            }
            else
            {
                return obj.GetType().GetProperty(namePart);
            }

        }

        public void SetPropertyValue(object obj, string name, object newValue)
        {
            var property = typeof(Address).GetProperty(name);
            property.SetValue(obj, newValue);
        }

        public void SetPropertyValueByPath(object obj, string path, object newValue)
        {
            var pathSegments = path.Split(".");

            if (pathSegments.Length == 1)
            {
                SetPropertyValue(obj, pathSegments[0], newValue);
            }
            else
            {
                ////  If more than one remaining segment, recurse
                var child = GetPropertyValue(obj, pathSegments[0]);

                SetPropertyValueByPath(child, String.Join(".", pathSegments.Skip(1)), newValue);
            }
        }

        public class Person
        {
            public string Name { get; set; }
            public IList<Address> Addresses { get; set; }
        }

        public class Address
        {
           public string Address1 { get; set; }
        }
}

最佳答案

Type.GetGenericArguments()不会像我认为你假设的那样做任何事情。

你在这里想要的是递归。给定 ”Foo.Bar[1].Baz”,得到 Foo。从中获取 Bar[1]。从其父级的 Baz 获取 PropertyInfo,使用它来设置 Bar[1] 属性的 Baz 属性的值

分解:

  1. 写一个“破解”一个属性名的方法,用out参数返回名称部分和索引值部分:“IndexedProperty[1]”进去; “IndexedProperty”和整数 1 出来了。 "FooBar" 进去,"FooBar"null 出来。如果有索引器则返回 true,否则返回 false。

    bool CrackPropertyName(string name, out string namePart, out object indexPart)
    
  2. 编写一个方法,它接受一个对象和一个字符串“PropertyName”或“IndexedPropety[0]”(不是路径 -- 没有点)并返回该对象的属性值。它使用 CrackPropertyName() 来简化其工作。

    object GetPropertyValue(object obj, string name)
    
  3. 编写一个按名称设置属性值的方法(不是按路径,只是按名称)。同样,它使用 CrackPropertyName() 来简化其工作。

    void SetPropertyValue(object obj, string name, object newValue)
    
  4. 使用上面的递归方法:

    void SetPropertyValueByPath(object obj, string path, object newvalue)
    {
        var pathSegments = /* split path on '.' */;
    
        if (pathSegments.Length == 1)
        {
            SetPropertyValue(obj, pathSegments[0], newValue); 
        }
        else
        {
            //  If more than one remaining segment, recurse
    
            var child = GetNamedPropertyvalue(obj, pathSegments[0]);
    
            return SetPropertyValueByPath(obj, String.Join(".", pathSegments.Skip(1)), newValue);
        }
    }
    

这些方法都很简单。由于无论如何您都在使用反射,因此您不妨全力以赴并编写一个设置任何属性的非泛型方法。

关于c# - 使用反射在嵌套对象中设置属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48082626/

相关文章:

c# - restclient 是否支持 json-patch?

nginx - 为什么Linux上的ASP.NET 5需要使用Kestrel?

ubuntu - 在 ServiceStack.Core 中,HttpContextAccessor.HttpContext 在 Linux 上为空,而在 Windows 上为非空

c# - 我应该如何在 wpf 和 C# 中绘制文本和形状?

c# - 将过滤器应用于 BindingSource,但它不起作用

c# - SendKeys.Send 并关闭键修饰符

javascript - 使用局部 View 、JQuery 和引导模式编辑记录

c# - 使用 0x80070005 将 .net 核心项目发布到 iis 时出现 HTTP 错误 500.19

linux - 使用 .NET Core 在 Linux 上更改文件所有者

unit-testing - 由于 "Illegal characters in path",.NET Core MSTest 失败