我如何重构这段 C# 代码
基本上我有一个方法,它接受一个对象并根据传入的对象设置另一个对象属性。
例如:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
听起来像是 AutoMapper 的工作
Sounds like a job for AutoMapper
使用反射来做到这一点。可能有这样的方法:
use reflection to do it. probably have a method like this:
为此,我想到了一些策略,它们都有自己的优点和缺点。另外,我也不熟悉它,但是 AutoMapper 工具链接到 对你的问题的不同答案听起来也可以是一个很好的解决方案。 (另外,如果有任何方法可以从同一个类派生所有类,或者将属性本身存储在结构中而不是直接存储在类中,这些似乎也是需要考虑的事情
。
) href="https://stackoverflow.com/questions/7118521/how-can-i-refactor-this-c-code/7118590#7118590">这个答案。但是,我不确定我完全理解该答案中函数的预期用途,因为我没有看到 GetValue 调用,也没有看到两种类型之间的任何映射。另外,我可以看到有时您可能想要创建一些东西来允许两个不同的名称相互映射,或者在两种类型之间进行转换。对于一个相当通用的解决方案,我可能会采用这样的方法:
示例代码
要复制到的
类:要复制的类,带映射信息到其他对象:
映射扩展类:
测试方法:
公共无效运行测试()
{
MyClass Test1 = new MyClass();
输出:
注意:如果您需要从另一个方向复制回来,或者想要使用代码生成器创建映射,您可能希望将映射放在其他地方的单独静态变量中,通过两种类型,而不是直接将映射存储在类中。如果您想反转方向,您可能只需在现有代码中添加一个标志,即可反转表和传递类型的含义,尽管我建议将对 TToType 和 TFromType 的引用更改为 TType1 和 TType2。
为了让代码生成器生成映射,将其分离到具有两个泛型类型参数的单独类中可能会更容易一些,这样您就不必担心将这些定义直接放在您的类中;当我编写代码时,我对如何做到这一点感到困惑,但我确实认为这可能是一个更灵活的选择。 (另一方面,这可能意味着需要更大的整体结构,这就是为什么我像一开始一样将其分解出来。)这种方式的另一个优点是,您不一定需要一次创建所有内容,您可以对成员变量进行可以想象的更改,以便您可以根据需要动态创建它们,可能通过函数参数或通过对象上的附加接口。
代码生成
这可以单独使用,也可以用作与接下来两种方法之一结合使用的工具。但想法是,您可以创建 CSV 或使用其他方法来保存映射数据,然后您可以创建带有占位符的类模板(在单独的文件或代码中)(例如,
${PROPERTIES_LIST}
),您可以使用它来执行.Replace()
操作,或者您可以创建用于在接下来的类型化数据集生成的.xsd
文件的模板部分,生成单独的行列表中的 .xsd,或者,最后您可以创建保存映射信息的结构,类似于我在反射部分中给出的解决方案。我过去做过的一件非常方便的事情就是创建一个带有表结构的类型化数据集,该数据集可以保存我生成代码所需的所有信息,并使用旧的 .NET 2.0 版本的 GridView允许我输入数据。但即使只有一个简单的 XmlDocument 或 CSV 文件,输入属性映射并读回它们也应该非常容易,并且对于任何场景都不会生成任何内容这里很可能与必须手动输入一次或两次相比,通常需要付出极大的努力,而且如果它经常更新,您最终将节省自己的手动编码错误和调试时间。
类型化数据集
虽然它有点超出了数据集的预期用途,但如果您不需要超高性能,它还是可以派上用场的。在数据集中定义表并构建后,您将拥有一组强类型对象,例如,可以使用它们来保存表示表和列或行的对象。因此,您可以使用与对象匹配的列名称和类型轻松地从头开始创建类型化数据集,然后循环遍历列名称以获取属性的名称。虽然不如反射那么强大,但它可能是一种非常快速的方法,可以让您复制具有相同属性名称的两个对象。当然,它的用处要有限得多,因为只有当您可以控制类型并且没有任何特别复杂的复制数据需求时它才会有用。
但它也可以让您做一些事情:
只需很少的额外努力。而且,如果您希望能够将对象保存到磁盘,只需将行添加到表中,您就有了使用
DataSet.ReadXml
序列化数据的好方法。 code> 和DataSet.WriteXml
调用。此外,对于反射解决方案,只要您的要求不太复杂,您就可以通过创建一个类型化数据集来创建映射定义,该数据集包含几个表,其列名称与要映射的列名称相匹配,然后使用 <用于执行映射定义的表的 code>DataColumn,而不是
Dictionaries
、Lists
和Tuples
。使用表中某种预定义的名称前缀或其他未使用的类型,您应该能够复制示例代码中的大部分功能。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:
Example Code
classes to be copied to:
class to be copied, w/ mapping info to the other objects:
Mapping Extension Class:
Test Method:
public void RunTest()
{
MyClass Test1 = new MyClass();
Output:
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:
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
andDataSet.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
DataColumn
s of the tables to do your mapping definition instead ofDictionaries
,Lists
, andTuples
. 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.