[英]How can I refactor this C# code
基本上我有一個方法,它接受一個對象,並根據傳入的對象設置另一個對象屬性。
例如:
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
//...
//...
}
聽起來像是AutoMapper的工作
用反射來做。 可能有這樣的方法:
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);
}
}
我想到了這幾個策略,都有各自的優點和缺點。 此外,我不熟悉它,但AutoMapper工具鏈接到您的問題的不同答案聽起來像它也可能是一個很好的解決方案。 (另外,如果有任何方法可以從同一個類派生所有類,或者將屬性本身存儲在結構中而不是直接存儲在類中,那么這些似乎也需要考慮。)
在這個答案中提到了這一點 。 但是,我不確定我是否完全理解該答案中函數的預期用途,因為我沒有看到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
// ------------------------------------------------------------------------
}
}
測試方法: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("-------------------------------------");
}
}
輸出:
-------------------------------------
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的各行,或者最后你可以創建保存映射信息的結構,例如我在反射部分給出的解決方案。
我過去做過的一件非常方便的事情就是用表格結構打造一個類型化的數據集,該表格結構可以保存我生成代碼所需的所有信息,並使用GridView的舊.NET 2.0版本允許我輸入數據。但是即使只有一個簡單的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
和DataSet.WriteXml
調用來序列化數據。 。
此外,對於反射解決方案,只要您的要求不是太復雜,您可以通過創建一個類型化數據集來創建映射定義,該數據集包含幾個列名稱與要映射的列匹配的表,然后使用DataColumn
的信息用於執行映射定義的表而不是Dictionaries
, Lists
和Tuples
。 使用表中的某種預定義名稱前綴或其他未使用的類型,您應該能夠復制示例代碼中的大多數功能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.