我如何重构这段 C# 代码

发布于 2024-11-30 18:25:21 字数 1893 浏览 0 评论 0原文

基本上我有一个方法,它接受一个对象并根据传入的对象设置另一个对象属性。

例如:

    private void SetObject(MyClass object)
{
  MyClass2 object2 = new MyClass2();
  object2.Property1 = HelperClass.Convert(object.Property1);
  //....
  // Lots more code ....
  //....
}

现在该方法有 53 行长,因为有很多属性需要设置。这个方法对我来说似乎太长了,但我正在努力弄清楚如何分解它。

一种选择是尝试将相似的属性分组,并将对象作为对设置这些相似属性的不同方法的引用传递,但这似乎不适合我。

或者我可以为 MyClass2 创建一个接受 MyClass1 的构造函数,但这似乎也不正确。

无论如何,欢迎提出一些建议。

编辑:好的,谢谢您的回复,我必须提供更多信息,属性名称不一样,我还必须调用一些转换方法。由于这个原因,反射不会很好,而且性能也会受到影响。我认为 Automapper 也是出于同样的原因。

代码的真实示例:

    private ReportType GetReportFromItem(SPWeb web, SPListItem item)
            {
                ReportType reportType = new ReportType();
                reportType.ReportID = int.Parse(item["Report ID"].ToString());
                reportType.Name = item["Title"].ToString();
                reportType.SourceLocation = FieldHelpers.GetUri(item["Source Location"]);
                reportType.TargetLocation = FieldHelpers.GetUri(item["Document Library"]);
                SPFieldUserValue group1 = 
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 1"));
                reportType.SecurityGroup1 = group1.LookupValue;
                SPFieldUserValue group2 =
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 2"));
                reportType.SecurityGroup2 = group2.LookupValue;
                SPFieldUserValue group3 =
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 3"));
                reportType.SecurityGroup3 = group3.LookupValue;
                SPFieldUserValue group4 =
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 4"));
// More code
//...
//...
}

Basically I have a method that takes an object and sets another objects properties based on the object passed in.

e.g:

    private void SetObject(MyClass object)
{
  MyClass2 object2 = new MyClass2();
  object2.Property1 = HelperClass.Convert(object.Property1);
  //....
  // Lots more code ....
  //....
}

Now the method is 53 lines long because there are alot of properties to set. The method seems too long to me but I'm struggling to work out how I can possibly break it down.

One option is to try and group up similar properties and pass the object around as a reference to different methods that set these similar properties, but that doesn't seem to sit right with me.

Or I could create a constructor for MyClass2 that accepts a MyClass1 but that doesn't seem right either.

Anyway would welcome some suggestions.

EDIT: Ok thanks for the replies I'll have to give more info, the property names arent the same and I have to call some conversion methods as well. Reflection wouldn't be good because of this and also the performance hit. Automapper I think for the same reasons.

A real example of the code:

    private ReportType GetReportFromItem(SPWeb web, SPListItem item)
            {
                ReportType reportType = new ReportType();
                reportType.ReportID = int.Parse(item["Report ID"].ToString());
                reportType.Name = item["Title"].ToString();
                reportType.SourceLocation = FieldHelpers.GetUri(item["Source Location"]);
                reportType.TargetLocation = FieldHelpers.GetUri(item["Document Library"]);
                SPFieldUserValue group1 = 
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 1"));
                reportType.SecurityGroup1 = group1.LookupValue;
                SPFieldUserValue group2 =
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 2"));
                reportType.SecurityGroup2 = group2.LookupValue;
                SPFieldUserValue group3 =
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 3"));
                reportType.SecurityGroup3 = group3.LookupValue;
                SPFieldUserValue group4 =
                    new SPFieldUserValue(web, FieldHelpers.GetStringFieldValue(item, "Security Group 4"));
// More code
//...
//...
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

请止步禁区 2024-12-07 18:25:21

听起来像是 AutoMapper 的工作

Sounds like a job for AutoMapper

无声无音无过去 2024-12-07 18:25:21

使用反射来做到这一点。可能有这样的方法:

private void SetProperties<T>(List<T> objects, List<Tuple<string, object>> propsAndValues) where T:<your_class>
        {
            Type type = typeof(T);
            var propInfos = propsAndValues.ToDictionary(key => type.GetProperty(key.Item1, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.SetProperty), elem => elem.Item2);

            objects.AsParallel().ForAll(obj =>
                {
                    obj.SetProps(propInfos);                                  
                });

        }

public static void SetProps<T>(this T obj, Dictionary<PropertyInfo, object> propInfos) where T : <your_class>
        {
            foreach (var propInfo in propInfos)
            {
                propInfo.Key.SetValue(obj, propInfo.Value, null);
            }            
        }

use reflection to do it. probably have a method like this:

private void SetProperties<T>(List<T> objects, List<Tuple<string, object>> propsAndValues) where T:<your_class>
        {
            Type type = typeof(T);
            var propInfos = propsAndValues.ToDictionary(key => type.GetProperty(key.Item1, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.SetProperty), elem => elem.Item2);

            objects.AsParallel().ForAll(obj =>
                {
                    obj.SetProps(propInfos);                                  
                });

        }

public static void SetProps<T>(this T obj, Dictionary<PropertyInfo, object> propInfos) where T : <your_class>
        {
            foreach (var propInfo in propInfos)
            {
                propInfo.Key.SetValue(obj, propInfo.Value, null);
            }            
        }
雨后彩虹 2024-12-07 18:25:21

为此,我想到了一些策略,它们都有自己的优点和缺点。另外,我也不熟悉它,但是 AutoMapper 工具链接到 对你的问题的不同答案听起来也可以是一个很好的解决方案。 (另外,如果有任何方法可以从同一个类派生所有类,或者将属性本身存储在结构中而不是直接存储在类中,这些似乎也是需要考虑的事情

) href="https://stackoverflow.com/questions/7118521/how-can-i-refactor-this-c-code/7118590#7118590">这个答案。但是,我不确定我完全理解该答案中函数的预期用途,因为我没有看到 GetValue 调用,也没有看到两种类型之间的任何映射。另外,我可以看到有时您可能想要创建一些东西来允许两个不同的名称相互映射,或者在两种类型之间进行转换。对于一个相当通用的解决方案,我可能会采用这样的方法:

  • 使用一个或多个方法创建一个扩展类,这些方法将使用反射根据相同的属性名称和/或预定义的配置对象进行复制。
  • 对于每对具有不同名称的属性的类型,创建一个配置对象,将名称相互映射。
  • 对于您不想复制的属性,请创建一个配置对象,其中包含要忽略的名称列表。
  • 如果目的是复制属性,我实际上没有看到在构造函数中将一个类传递给另一个类有什么令人反感的事情,这似乎更多是一种风格问题,而不是任何硬性和快速的事情。

示例代码

要复制到的

public class MyClass2
{
    public int Property1 { get; set; }
    public int Property2 { get; set; }
    public string Property3WithOtherName { get; set; }
    public double Property4 { get; set; }
    public string Property5WithDifferentName { get; set; }

    public string TestIntToString { get; set; }
    public int TestStringToInt { get; set; }
}

public class MyClass3
{
    public int Prop1 { get; set; }
    public int Prop2 { get; set; }
    public string Prop3OtherName { get; set; }
    public double Prop4 { get; set; }
    public string Prop5DiffName { get; set; }
    public string PropOnlyClass3 { get; set; }
    public string[] StringArray { get; set; }
}

类:要复制的类,带映射信息到其他对象:

public class MyClass
{
    public int Property1 { get; set; }
    public int Property2 { get; set; }
    public string Property3 { get; set; }
    public double Property4 { get; set; }
    public string Property5 { get; set; }

    public double PropertyDontCopy { get; set; }
    public string PropertyOnlyClass3 { get; set; }
    public int[] PropertyIgnoreMe { get; set; }

    public string[] StringArray { get; set; }

    public int TestIntToString { get; set; }
    public string TestStringToInt { get; set; }

    # region Static Property Mapping Information
        // this is one possibility for creating and storing the mapping
       // information: the class uses two dictionaries, one that links 
       // the other type with a dictionary of mapped properties, and 
       // one that links the other type with a list of excluded ones.
        public static Dictionary<Type, Dictionary<string, string>>
            PropertyMappings =
                new Dictionary<Type, Dictionary<string, string>>
                {
                    {
                        typeof(MyClass2),
                        new Dictionary<string, string>
                        {
                            { "Property3", "Property3WithOtherName" },
                            { "Property5", "Property5WithDifferentName" },
                        }
                    },
                    {
                        typeof(MyClass3),
                        new Dictionary<string, string>
                        {
                            { "Property1", "Prop1" },
                            { "Property2", "Prop2" },
                            { "Property3", "Prop3OtherName" },
                            { "Property4", "Prop4" },
                            { "Property5", "Prop5DiffName" },
                            { "PropertyOnlyClass3", "PropOnlyClass3" },
                        }
                    },
                };

        public static Dictionary<Type, List<string>>
            UnmappedProperties =
                new Dictionary<Type, List<string>>
                {
                    {
                        typeof(MyClass2),
                        new List<string> 
                            {
                                "PropertyDontCopy",
                                "PropertyOnlyClass3",
                                "PropertyIgnoreMe"
                            }
                    },
                    {
                        typeof(MyClass3),
                        new List<string> 
                            {
                                "PropertyDontCopy", 
                                "PropertyIgnoreMe"
                            }
                    }
                };

        // this function pulls together an individual property mapping
        public static Tuple<Dictionary<string, string>, List<string>>
            MapInfo<TOtherType>()
            {
                return 
                    new Tuple<Dictionary<string,string>,List<string>>
                    (
                        PropertyMappings[typeof(TOtherType)],
                        UnmappedProperties[typeof(TOtherType)]
                    );
            }

    #endregion
}

映射扩展类:

public static class MappingExtensions
{
    // this is one possibility for setting up object mappings
    #region Type Map Definition Section
        // * set up the MapInfo<TOther>() call in each object to map
        // * setup as follows to map two types to an actual map
        public static Tuple<Type, Type> MapFromTo<TFromType, TToType>()
        {
            return Tuple.Create<Type,Type>(typeof(TFromType), typeof(TToType));
        }

        static Dictionary<
            Tuple<Type, Type>,
            Tuple<Dictionary<string, string>, List<string>>
        >
        MappingDefinitions =
            new Dictionary <
                Tuple<Type,Type>,
                Tuple<Dictionary<string,string>,List<string>>
            > 
            {
                { MapFromTo<MyClass,MyClass2>(), MyClass.MapInfo<MyClass2>() },
                { MapFromTo<MyClass,MyClass3>(), MyClass.MapInfo<MyClass3>() },
            };
    #endregion

    // method using Reflection.GetPropertyInfo and mapping info to do copying
    // * for fields you will need to reflect using GetFieldInfo() instead
    // * for both you will need to reflect using GetMemberInfo() instead
    public static void CopyFrom<TFromType, TToType>(
            this TToType parThis,
            TFromType parObjectToCopy
        )
    {
        var Map = MappingDefinitions[MapFromTo<TFromType, TToType>()];

        Dictionary<string,string> MappedNames = Map.Item1;
        List<string> ExcludedNames = Map.Item2;

        Type FromType = typeof(TFromType);  Type ToType = typeof(TToType);

        // ------------------------------------------------------------------------
        // Step 1: Collect PIs for TToType and TFromType for Copying

        // ------------------------------------------------------------------------
        // Get PropertyInfos for TToType

        // the desired property types to reflect for ToType
        var ToBindings =
            BindingFlags.Public | BindingFlags.NonPublic  // property visibility 
            | BindingFlags.Instance                       // instance properties
            | BindingFlags.SetProperty;                   // sets for ToType

        // reflect an array of all properties for this type
        var ToPIs = ToType.GetProperties(ToBindings);

        // checks for mapped properties or exclusions not defined for the class
        #if DEBUG
            var MapErrors =
                from name in 
                    MappedNames.Values
                where !ToPIs.Any(pi => pi.Name == name)
                select string.Format(
                    "CopyFrom<{0},{1}>: mapped property '{2}' not defined for {1}",
                    FromType.Name, ToType.Name, name
                );
        #endif

        // ------------------------------------------------------------------------
        // Get PropertyInfos for TFromType

        // the desired property types to reflect; if you want to use fields, too, 
        //   you can do GetMemberInfo instead of GetPropertyInfo below
        var FromBindings =
            BindingFlags.Public | BindingFlags.NonPublic  // property visibility 
            | BindingFlags.Instance                       // instance/static
            | BindingFlags.GetProperty;                   // gets for FromType

        // reflect all properties from the FromType
        var FromPIs = FromType.GetProperties(FromBindings);

        // checks for mapped properties or exclusions not defined for the class
        #if DEBUG
            MapErrors = MapErrors.Concat(
                from mn in MappedNames.Keys.Concat(
                    ExcludedNames)
                where !FromPIs.Any(pi => pi.Name == mn)
                select string.Format(
                    "CopyFrom<{0},{1}>: mapped property '{2}' not defined for {1}",
                    FromType.Name, ToType.Name, mn
                )
            );

            // if there were any errors, aggregate and throw 
            if (MapErrors.Count() > 0)
                throw new Exception(
                    MapErrors.Aggregate(
                        "", (a,b)=>string.Format("{0}{1}{2}",a,Environment.NewLine,b)
                ));
        #endif

        // exclude anything in the exclusions or not in the ToPIs
        FromPIs = FromPIs.Where(
            fromPI => 
                !ExcludedNames.Contains(fromPI.Name)
                && ToPIs.Select(toPI => toPI.Name).Concat(MappedNames.Keys)
                    .Contains(fromPI.Name)
        )
        .ToArray();

        // Step 1 Complete 
        // ------------------------------------------------------------------------

        // ------------------------------------------------------------------------
        // Step 2: Copy Property Values from Source to Destination 

        #if DEBUG
            Console.WriteLine("Copying " + FromType.Name + " to " + ToType.Name);
        #endif

        // we're using FromPIs to drive the loop because we've already elimiated 
        // all items that don't have a matching value in ToPIs
        foreach (PropertyInfo FromPI in FromPIs)
        {
            PropertyInfo ToPI;

            // if the 'from' property name exists in the mapping, use the mapped 
            //   name to find ToPI, otherwise use ToPI matching the 'from' name
            if (MappedNames.Keys.Contains(FromPI.Name))
                ToPI = ToPIs.First(pi => pi.Name == MappedNames[FromPI.Name]);
            else
                ToPI = ToPIs.First(pi => pi.Name == FromPI.Name);

            Type FromPropertyType = FromPI.PropertyType;
            Type ToPropertyType = ToPI.PropertyType;

            // retrieve the property value from the object we're copying from; keep
            // in mind if this copies by-reference for arrays and other ref types,
            // so you will need to deal with it if you want other behavior
            object PropertyValue = FromPI.GetValue(parObjectToCopy, null);

            // only need this if there are properties with incompatible types
            // * implement IConvertible for user-defined types to allow conversion
            // * you can try/catch if you want to ignore items which don't convert
            if (!ToPropertyType.IsAssignableFrom(FromPropertyType))
                PropertyValue = Convert.ChangeType(PropertyValue, ToPropertyType);

            // set the property value on the object we're copying to
            ToPI.SetValue(parThis, PropertyValue, null);

            #if DEBUG
                Console.WriteLine(
                    "\t"
                    + "(" + ToPI.PropertyType.Name + ")" + ToPI.Name
                    + " := "
                    + "(" + FromPI.PropertyType.Name + ")" + FromPI.Name 
                    + " == " 
                    + ((ToPI.PropertyType.Name == "String") ? "'" : "")
                    + PropertyValue.ToString()
                    + ((ToPI.PropertyType.Name == "String") ? "'" : "")
                );
            #endif
        }

        // Step 2 Complete
        // ------------------------------------------------------------------------
    }
}

测试方法:
公共无效运行测试()
{
MyClass Test1 = new MyClass();

        Test1.Property1 = 1;
        Test1.Property2 = 2;
        Test1.Property3 = "Property3String";
        Test1.Property4 = 4.0;
        Test1.Property5 = "Property5String";

        Test1.PropertyDontCopy = 100.0;
        Test1.PropertyIgnoreMe = new int[] { 0, 1, 2, 3 };
        Test1.PropertyOnlyClass3 = "Class3OnlyString";
        Test1.StringArray = new string[] { "String0", "String1", "String2" };

        Test1.TestIntToString = 123456;
        Test1.TestStringToInt = "654321";

        Console.WriteLine("-------------------------------------");
        Console.WriteLine("Copying: Test1 to Test2");
        Console.WriteLine("-------------------------------------");

        MyClass2 Test2 = new MyClass2();
        Test2.CopyFrom(Test1);

        Console.WriteLine("-------------------------------------");
        Console.WriteLine("Copying: Test1 to Test3");
        Console.WriteLine("-------------------------------------");

        MyClass3 Test3 = new MyClass3();
        Test3.CopyFrom(Test1);

        Console.WriteLine("-------------------------------------");
        Console.WriteLine("Done");
        Console.WriteLine("-------------------------------------");
    }
}

输出:

-------------------------------------
Copying: Test1 to Test2
-------------------------------------
Copying MyClass to MyClass2
    (Int32)Property1 := (Int32)Property1 == 1
    (Int32)Property2 := (Int32)Property2 == 2
    (String)Property3WithOtherName := (String)Property3 == 'Property3String'
    (Double)Property4 := (Double)Property4 == 4
    (String)Property5WithDifferentName := (String)Property5 == 'Property5String'
    (String)TestIntToString := (Int32)TestIntToString == '123456'
    (Int32)TestStringToInt := (String)TestStringToInt == 654321
-------------------------------------
Copying: Test1 to Test3
-------------------------------------
Copying MyClass to MyClass3
    (Int32)Prop1 := (Int32)Property1 == 1
    (Int32)Prop2 := (Int32)Property2 == 2
    (String)Prop3OtherName := (String)Property3 == 'Property3String'
    (Double)Prop4 := (Double)Property4 == 4
    (String)Prop5DiffName := (String)Property5 == 'Property5String'
    (String)PropOnlyClass3 := (String)PropertyOnlyClass3 == 'Class3OnlyString'
    (String[])StringArray := (String[])StringArray == System.String[]
-------------------------------------
Done
-------------------------------------

注意:如果您需要从另一个方向复制回来,或者想要使用代码生成器创建映射,您可能希望将映射放在其他地方的单独静态变量中,通过两种类型,而不是直接将映射存储在类中。如果您想反转方向,您可能只需在现有代码中添加一个标志,即可反转表和传递类型的含义,尽管我建议将对 TToType 和 TFromType 的引用更改为 TType1 和 TType2。

为了让代码生成器生成映射,将其分离到具有两个泛型类型参数的单独类中可能会更容易一些,这样您就不必担心将这些定义直接放在您的类中;当我编写代码时,我对如何做到这一点感到困惑,但我确实认为这可能是一个更灵活的选择。 (另一方面,这可能意味着需要更大的整体结构,这就是为什么我像一开始一样将其分解出来。)这种方式的另一个优点是,您不一定需要一次创建所有内容,您可以对成员变量进行可以想象的更改,以便您可以根据需要动态创建它们,可能通过函数参数或通过对象上的附加接口。

代码生成

这可以单独使用,也可以用作与接下来两种方法之一结合使用的工具。但想法是,您可以创建 CSV 或使用其他方法来保存映射数据,然后您可以创建带有占位符的类模板(在单独的文件或代码中)(例如,${PROPERTIES_LIST}),您可以使用它来执行 .Replace() 操作,或者您可以创建用于在接下来的类型化数据集生成的 .xsd 文件的模板部分,生成单独的行列表中的 .xsd,或者,最后您可以创建保存映射信息的结构,类似于我在反射部分中给出的解决方案。

我过去做过的一件非常方便的事情就是创建一个带有表结构的类型化数据集,该数据集可以保存我生成代码所需的所有信息,并使用旧的 .NET 2.0 版本的 GridView允许我输入数据。但即使只有一个简单的 XmlDocument 或 CSV 文件,输入属性映射并读回它们也应该非常容易,并且对于任何场景都不会生成任何内容这里很可能与必须手动输入一次或两次相比,通常需要付出极大的努力,而且如果它经常更新,您最终将节省自己的手动编码错误和调试时间。

类型化数据集

虽然它有点超出了数据集的预期用途,但如果您不需要超高性能,它还是可以派上用场的。在数据集中定义表并构建后,您将拥有一组强类型对象,例如,可以使用它们来保存表示表和列或行的对象。因此,您可以使用与对象匹配的列名称和类型轻松地从头开始创建类型化数据集,然后循环遍历列名称以获取属性的名称。虽然不如反射那么强大,但它可能是一种非常快速的方法,可以让您复制具有相同属性名称的两个对象。当然,它的用处要有限得多,因为只有当您可以控制类型并且没有任何特别复杂的复制数据需求时它才会有用。

但它也可以让您做一些事情:

MyClassDataRow Object1 = MyDataSet.MyClassTable.NewRow();

Object1.Prop1 = 123;
Object2.Prop2 = "Hello Dataset";
// etc...

MyClass2DataRow Object2 = MyDataSet.MyClass2Table.NewRow();

foreach (DataColumn ColumnName in MyClassTable.Columns) 
    Object2[ColumnName] = Object1[ColumnName];

只需很少的额外努力。而且,如果您希望能够将对象保存到磁盘,只需将行添加到表中,您就有了使用 DataSet.ReadXml序列化数据的好方法。 code> 和 DataSet.WriteXml 调用。

此外,对于反射解决方案,只要您的要求不太复杂,您就可以通过创建一个类型化数据集来创建映射定义,该数据集包含几个表,其列名称与要映射的列名称相匹配,然后使用 <用于执行映射定义的表的 code>DataColumn,而不是 DictionariesListsTuples。使用表中某种预定义的名称前缀或其他未使用的类型,您应该能够复制示例代码中的大部分功能。

There are a few strategies that came to my mind for doing this, all with their own advantages and disadvantages. Also, I was not familiar with it, but the AutoMapper tool linked to in a different answer to your question sounds like it also could be a good solution. (Also, if there were any way of deriving your classes all from the same class, or storing the properties themselves in a struct instead of directly in the classes, those seem like things to consider as well.)

Reflection

This was mentioned in this answer. However, I am not sure I totally understand the intended use of the functions in that answer, since I don't see a GetValue call, nor any mapping between two types. Also, I can see times where you might want to create something to allow for two different names to map to one another, or for conversion between two types. For a rather generic solution, I would probably go with an approach like this:

  • Create an extension class with one or more methods that will use reflection to copy based on identical property names and/or pre-defined configuration objects.
  • For each pair of types that has properties that won't have identical names, create a configuration object mapping the names to each other.
  • For properties that you won't want to be copied, create a configuration object that holds a list of names to ignore.
  • I don't actually see anything objectionable about passing one class to another in the constructor if the intent is to copy the properties, seems more of a matter of style than anything hard and fast.

Example Code

classes to be copied to:

public class MyClass2
{
    public int Property1 { get; set; }
    public int Property2 { get; set; }
    public string Property3WithOtherName { get; set; }
    public double Property4 { get; set; }
    public string Property5WithDifferentName { get; set; }

    public string TestIntToString { get; set; }
    public int TestStringToInt { get; set; }
}

public class MyClass3
{
    public int Prop1 { get; set; }
    public int Prop2 { get; set; }
    public string Prop3OtherName { get; set; }
    public double Prop4 { get; set; }
    public string Prop5DiffName { get; set; }
    public string PropOnlyClass3 { get; set; }
    public string[] StringArray { get; set; }
}

class to be copied, w/ mapping info to the other objects:

public class MyClass
{
    public int Property1 { get; set; }
    public int Property2 { get; set; }
    public string Property3 { get; set; }
    public double Property4 { get; set; }
    public string Property5 { get; set; }

    public double PropertyDontCopy { get; set; }
    public string PropertyOnlyClass3 { get; set; }
    public int[] PropertyIgnoreMe { get; set; }

    public string[] StringArray { get; set; }

    public int TestIntToString { get; set; }
    public string TestStringToInt { get; set; }

    # region Static Property Mapping Information
        // this is one possibility for creating and storing the mapping
       // information: the class uses two dictionaries, one that links 
       // the other type with a dictionary of mapped properties, and 
       // one that links the other type with a list of excluded ones.
        public static Dictionary<Type, Dictionary<string, string>>
            PropertyMappings =
                new Dictionary<Type, Dictionary<string, string>>
                {
                    {
                        typeof(MyClass2),
                        new Dictionary<string, string>
                        {
                            { "Property3", "Property3WithOtherName" },
                            { "Property5", "Property5WithDifferentName" },
                        }
                    },
                    {
                        typeof(MyClass3),
                        new Dictionary<string, string>
                        {
                            { "Property1", "Prop1" },
                            { "Property2", "Prop2" },
                            { "Property3", "Prop3OtherName" },
                            { "Property4", "Prop4" },
                            { "Property5", "Prop5DiffName" },
                            { "PropertyOnlyClass3", "PropOnlyClass3" },
                        }
                    },
                };

        public static Dictionary<Type, List<string>>
            UnmappedProperties =
                new Dictionary<Type, List<string>>
                {
                    {
                        typeof(MyClass2),
                        new List<string> 
                            {
                                "PropertyDontCopy",
                                "PropertyOnlyClass3",
                                "PropertyIgnoreMe"
                            }
                    },
                    {
                        typeof(MyClass3),
                        new List<string> 
                            {
                                "PropertyDontCopy", 
                                "PropertyIgnoreMe"
                            }
                    }
                };

        // this function pulls together an individual property mapping
        public static Tuple<Dictionary<string, string>, List<string>>
            MapInfo<TOtherType>()
            {
                return 
                    new Tuple<Dictionary<string,string>,List<string>>
                    (
                        PropertyMappings[typeof(TOtherType)],
                        UnmappedProperties[typeof(TOtherType)]
                    );
            }

    #endregion
}

Mapping Extension Class:

public static class MappingExtensions
{
    // this is one possibility for setting up object mappings
    #region Type Map Definition Section
        // * set up the MapInfo<TOther>() call in each object to map
        // * setup as follows to map two types to an actual map
        public static Tuple<Type, Type> MapFromTo<TFromType, TToType>()
        {
            return Tuple.Create<Type,Type>(typeof(TFromType), typeof(TToType));
        }

        static Dictionary<
            Tuple<Type, Type>,
            Tuple<Dictionary<string, string>, List<string>>
        >
        MappingDefinitions =
            new Dictionary <
                Tuple<Type,Type>,
                Tuple<Dictionary<string,string>,List<string>>
            > 
            {
                { MapFromTo<MyClass,MyClass2>(), MyClass.MapInfo<MyClass2>() },
                { MapFromTo<MyClass,MyClass3>(), MyClass.MapInfo<MyClass3>() },
            };
    #endregion

    // method using Reflection.GetPropertyInfo and mapping info to do copying
    // * for fields you will need to reflect using GetFieldInfo() instead
    // * for both you will need to reflect using GetMemberInfo() instead
    public static void CopyFrom<TFromType, TToType>(
            this TToType parThis,
            TFromType parObjectToCopy
        )
    {
        var Map = MappingDefinitions[MapFromTo<TFromType, TToType>()];

        Dictionary<string,string> MappedNames = Map.Item1;
        List<string> ExcludedNames = Map.Item2;

        Type FromType = typeof(TFromType);  Type ToType = typeof(TToType);

        // ------------------------------------------------------------------------
        // Step 1: Collect PIs for TToType and TFromType for Copying

        // ------------------------------------------------------------------------
        // Get PropertyInfos for TToType

        // the desired property types to reflect for ToType
        var ToBindings =
            BindingFlags.Public | BindingFlags.NonPublic  // property visibility 
            | BindingFlags.Instance                       // instance properties
            | BindingFlags.SetProperty;                   // sets for ToType

        // reflect an array of all properties for this type
        var ToPIs = ToType.GetProperties(ToBindings);

        // checks for mapped properties or exclusions not defined for the class
        #if DEBUG
            var MapErrors =
                from name in 
                    MappedNames.Values
                where !ToPIs.Any(pi => pi.Name == name)
                select string.Format(
                    "CopyFrom<{0},{1}>: mapped property '{2}' not defined for {1}",
                    FromType.Name, ToType.Name, name
                );
        #endif

        // ------------------------------------------------------------------------
        // Get PropertyInfos for TFromType

        // the desired property types to reflect; if you want to use fields, too, 
        //   you can do GetMemberInfo instead of GetPropertyInfo below
        var FromBindings =
            BindingFlags.Public | BindingFlags.NonPublic  // property visibility 
            | BindingFlags.Instance                       // instance/static
            | BindingFlags.GetProperty;                   // gets for FromType

        // reflect all properties from the FromType
        var FromPIs = FromType.GetProperties(FromBindings);

        // checks for mapped properties or exclusions not defined for the class
        #if DEBUG
            MapErrors = MapErrors.Concat(
                from mn in MappedNames.Keys.Concat(
                    ExcludedNames)
                where !FromPIs.Any(pi => pi.Name == mn)
                select string.Format(
                    "CopyFrom<{0},{1}>: mapped property '{2}' not defined for {1}",
                    FromType.Name, ToType.Name, mn
                )
            );

            // if there were any errors, aggregate and throw 
            if (MapErrors.Count() > 0)
                throw new Exception(
                    MapErrors.Aggregate(
                        "", (a,b)=>string.Format("{0}{1}{2}",a,Environment.NewLine,b)
                ));
        #endif

        // exclude anything in the exclusions or not in the ToPIs
        FromPIs = FromPIs.Where(
            fromPI => 
                !ExcludedNames.Contains(fromPI.Name)
                && ToPIs.Select(toPI => toPI.Name).Concat(MappedNames.Keys)
                    .Contains(fromPI.Name)
        )
        .ToArray();

        // Step 1 Complete 
        // ------------------------------------------------------------------------

        // ------------------------------------------------------------------------
        // Step 2: Copy Property Values from Source to Destination 

        #if DEBUG
            Console.WriteLine("Copying " + FromType.Name + " to " + ToType.Name);
        #endif

        // we're using FromPIs to drive the loop because we've already elimiated 
        // all items that don't have a matching value in ToPIs
        foreach (PropertyInfo FromPI in FromPIs)
        {
            PropertyInfo ToPI;

            // if the 'from' property name exists in the mapping, use the mapped 
            //   name to find ToPI, otherwise use ToPI matching the 'from' name
            if (MappedNames.Keys.Contains(FromPI.Name))
                ToPI = ToPIs.First(pi => pi.Name == MappedNames[FromPI.Name]);
            else
                ToPI = ToPIs.First(pi => pi.Name == FromPI.Name);

            Type FromPropertyType = FromPI.PropertyType;
            Type ToPropertyType = ToPI.PropertyType;

            // retrieve the property value from the object we're copying from; keep
            // in mind if this copies by-reference for arrays and other ref types,
            // so you will need to deal with it if you want other behavior
            object PropertyValue = FromPI.GetValue(parObjectToCopy, null);

            // only need this if there are properties with incompatible types
            // * implement IConvertible for user-defined types to allow conversion
            // * you can try/catch if you want to ignore items which don't convert
            if (!ToPropertyType.IsAssignableFrom(FromPropertyType))
                PropertyValue = Convert.ChangeType(PropertyValue, ToPropertyType);

            // set the property value on the object we're copying to
            ToPI.SetValue(parThis, PropertyValue, null);

            #if DEBUG
                Console.WriteLine(
                    "\t"
                    + "(" + ToPI.PropertyType.Name + ")" + ToPI.Name
                    + " := "
                    + "(" + FromPI.PropertyType.Name + ")" + FromPI.Name 
                    + " == " 
                    + ((ToPI.PropertyType.Name == "String") ? "'" : "")
                    + PropertyValue.ToString()
                    + ((ToPI.PropertyType.Name == "String") ? "'" : "")
                );
            #endif
        }

        // Step 2 Complete
        // ------------------------------------------------------------------------
    }
}

Test Method:
public void RunTest()
{
MyClass Test1 = new MyClass();

        Test1.Property1 = 1;
        Test1.Property2 = 2;
        Test1.Property3 = "Property3String";
        Test1.Property4 = 4.0;
        Test1.Property5 = "Property5String";

        Test1.PropertyDontCopy = 100.0;
        Test1.PropertyIgnoreMe = new int[] { 0, 1, 2, 3 };
        Test1.PropertyOnlyClass3 = "Class3OnlyString";
        Test1.StringArray = new string[] { "String0", "String1", "String2" };

        Test1.TestIntToString = 123456;
        Test1.TestStringToInt = "654321";

        Console.WriteLine("-------------------------------------");
        Console.WriteLine("Copying: Test1 to Test2");
        Console.WriteLine("-------------------------------------");

        MyClass2 Test2 = new MyClass2();
        Test2.CopyFrom(Test1);

        Console.WriteLine("-------------------------------------");
        Console.WriteLine("Copying: Test1 to Test3");
        Console.WriteLine("-------------------------------------");

        MyClass3 Test3 = new MyClass3();
        Test3.CopyFrom(Test1);

        Console.WriteLine("-------------------------------------");
        Console.WriteLine("Done");
        Console.WriteLine("-------------------------------------");
    }
}

Output:

-------------------------------------
Copying: Test1 to Test2
-------------------------------------
Copying MyClass to MyClass2
    (Int32)Property1 := (Int32)Property1 == 1
    (Int32)Property2 := (Int32)Property2 == 2
    (String)Property3WithOtherName := (String)Property3 == 'Property3String'
    (Double)Property4 := (Double)Property4 == 4
    (String)Property5WithDifferentName := (String)Property5 == 'Property5String'
    (String)TestIntToString := (Int32)TestIntToString == '123456'
    (Int32)TestStringToInt := (String)TestStringToInt == 654321
-------------------------------------
Copying: Test1 to Test3
-------------------------------------
Copying MyClass to MyClass3
    (Int32)Prop1 := (Int32)Property1 == 1
    (Int32)Prop2 := (Int32)Property2 == 2
    (String)Prop3OtherName := (String)Property3 == 'Property3String'
    (Double)Prop4 := (Double)Property4 == 4
    (String)Prop5DiffName := (String)Property5 == 'Property5String'
    (String)PropOnlyClass3 := (String)PropertyOnlyClass3 == 'Class3OnlyString'
    (String[])StringArray := (String[])StringArray == System.String[]
-------------------------------------
Done
-------------------------------------

NOTE: if you have use for copying back the other direction, or would like, for instance, to create your mappings with a code generator, you may want to go with having your mappings in a separate static variable somewhere else, mapped by both types, rather than storing your mappings directly within a class. If you want to reverse direction, you could probably just add a flag to the existing code that lets you invert the meanings of the tables and of the passed types, although I would recommend changing references to TToType and TFromType to TType1 and TType2.

For having a code generator generate the mapping it's probably a good bit easier to separate it into the separate class with two type parameters for the generic, so that you don't necessarily have to worry about putting those definitions directly within your classes; I was torn about how to do it when I wrote the code, but I do think that would probably have been a more flexible option. (On the other hand, it probably means needing larger overall structure, which is why I broke it out like I did in the first place.) Another advantage to this way is that you don't necessarily need to create everything all at once, you could conceivable change to member variables that let you create them on-the-fly as needed, possibly through a function param or through an additional interface on your object.

Code Generation

This can be used by itself, it can also be used as a tool in conjunction with either of the next two methods. But the idea is that you can create a CSV or use some other method to hold your mapping data, and then you can create class templates (in separate files or in code) with placeholders in them (e.g., ${PROPERTIES_LIST}) that you can use to do .Replace() operations on, or you can create templates of the .xsd files used for typed dataset generation in the next section, generate the individual lines of the .xsd from the list, or, finally you could create the structures that hold the mapping information for something like the solution I give in the reflection section.

One thing I've done in the past which was very handy was to just whip up a typed dataset with a table structure that can hold all the information I need to do my code generation, and use the old .NET 2.0 version of the GridView to allow me to enter the data in. But even with just a simple XmlDocument or CSV file(s), it should be very easy to enter your properties mappings and read them back in, and the none of the generation for any of the scenarios here is likely to be terribly much effort, usually even compared to having to type it all in by hand just once or twice, plus if it's something that gets updated at all often, you will eventually save yourself hand-coding errors and debug time as well.

Typed Data Sets

Although it's sort of beyond the intended use of the data set, if you don't need super-high performance, it can come in handy. After defining a table in the dataset and building, you have a set of strongly-typed objects that can be used, for instance, to hold objects representing the tables and columns or rows. So, you could easily create a typed dataset from scratch using column names and types matching your objects, and then loop through the column names to get the names of your properties. While not as powerful as reflection, it could be a very quick way of getting yourself set up to copy two objects with the same property names. Of course, it's a much more limited usefulness because it's only going to be useful if you have control over your types, and if you don't have any particularly complicated needs for copying the data.

But it also can let you do things like:

MyClassDataRow Object1 = MyDataSet.MyClassTable.NewRow();

Object1.Prop1 = 123;
Object2.Prop2 = "Hello Dataset";
// etc...

MyClass2DataRow Object2 = MyDataSet.MyClass2Table.NewRow();

foreach (DataColumn ColumnName in MyClassTable.Columns) 
    Object2[ColumnName] = Object1[ColumnName];

with little additional effort. And, if you're in a situation where you want to be able save your objects out to disk, just add the rows to the table you've got a great way of serializing the data with the DataSet.ReadXml and DataSet.WriteXml calls.

Also, for the reflection solution, as long as your requirements weren't too complicated, you could create your mapping definitions by creating a typed dataset with a couple of tables with column names matching those to map, and then using the information in the DataColumns of the tables to do your mapping definition instead of Dictionaries, Lists, and Tuples. Using some sort of predefined names prefixes or otherwise unused types in your table, you ought to be able to duplicate most of the functionality in the sample code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文