如何避免代码重复?

7

我有以下代码,我希望以最少的代码行数完成相同的工作。我该如何做到这一点?

List<Category> categoryList = new List<Category>();
categoryList = Category.LoadForProject(project.ID).ToList();
List<string> categories = new List<string>(Categories);
IList<Category> currentCategories = Category.LoadForProject(project.ID).ToList();
if (currentCategories != null)
{
    foreach (var existingCategories in currentCategories)
    {
        if (categories.Contains(existingCategories.Name))
           categories.Remove(existingCategories.Name);
        else
            existingCategories.Delete(Services.UserServices.User);
    }
    foreach (string item in categories)
    {
        Category category = new Category(project, item.ToString());
        category.Project = project;
        category.Save();
   }
}

List<string> priorities = new List<string>(Priorities);
IList<Priority> currentPriorities = Priority.LoadForProject(project.ID).ToList();
if (currentPriorities != null)
{
   foreach (var existingPriorities in currentPriorities)
   {
       if (priorities.Contains(existingPriorities.Name))
           priorities.Remove(existingPriorities.Name);
       else
           existingPriorities.Delete(Services.UserServices.User);
   }
   foreach (string item in priorities)
   {
       Priority priority = new Priority(project, item.ToString());
       priority.Project = project;
       priority.Save();
   }
}
6个回答

9
类似这样的代码应该可以实现:
public IList<T> DoYourThing<T>(IList<T> items, IList<T> currentItems, Project project) where T : CommonBaseType
{
  if (currentItems != null)
  {
    foreach (var existingItem in currentItems)
    {
      if (items.Contains(existingItem.Name))
        items.Remove(existingItem.Name);
      else
        existingItems.Delete(Services.UserServices.User);
    }
    foreach (string item in items)
    {
      T newItem = Activator.CreateInstance(typeof(T), new object[] {project, item.ToString()}) as T;
      newItem.Project = project;
      newItem.Save();
    }
  }

  return currentItems;
}

然后,您可以这样调用它:
var currentCategories = DoYourThing(Categories.ToList(), Category.LoadForProject(project.ID).ToList());
var currentProjects = DoYourThing(Priorities.ToList(), Priority.LoadForProject(project.ID).ToList());

最后,你需要特别注意两点: 首先,函数 where T : CommonBaseType 有一个通用条件。我假设 Category 和 Project 有一个包含 Name 的公共基础类型或接口。如果没有,你应该去掉这个条件并使用 Dynamic 来获取 Name。 其次,我使用 Activator.Create 为你创建类。这是一个棘手的部分,如果你不知道这个技巧,就很难理解。 祝你好运!

泛型是一个不错的解决方案... 你的调用需要添加类型,对吧,比如 var currentCategories = DoYourThing<Category>(Categories.ToList(), Category.LoadForProject(project.ID).ToList()); - Lazarus
@Lasarus:不需要。当类型可以通过参数推断时(就像这种情况),方法上的类型声明是多余的。 :) - Brian Genisio
@Brian Genisio,你说得对,如果你不理解Activator.Create部分,那就几乎不可能实现。这真是太简单了,天才啊。 - msarchet
1
这不应该是:Activator.CreateInstance 而不是 Activator.Create 吗? - Piers Myers
如果您想避免使用 Activator.CreateInstance 调用,而是选择一个可以在编译时验证的构造函数,您可以传递一个类型为 Func<Project,string,T> 的 'builder' 参数,该参数可以通过调用方法的调用者使用 lambda 语法轻松指定:(p,s) => new Category(p,s)。这还为调用者提供了额外的灵活性,例如,如果他们想要构建一些存根或派生版本的类别。 - Dan Bryant
非常感谢您的回复。我是C#的新手。您能否给我提供一些使用dynamic的代码片段,因为我没有共同的基础。谢谢! - learning

7

将Priority和Category实现相同的接口或从一个包含公共属性的类派生出来(即.Project,.Name和.Save)。然后使用该接口或基类作为函数的类型,您将能够将这两个类的集合传递给它。


@Lazarus正在编写代码,当我看到@Brian Genisio已经很好地阐述了这一点时,他已经完成了一半! - Iain Ward

1

好的,就我所理解的,您想要添加“新”列表中不存在于存储库中的类别/优先级。

请执行此操作。

public void SaveNewItems<T>(IList<string> newList, IList<T> currentList, string project)
    where T: new(), IStoreableItem
{
    //find only new items
    var toAdd = from itemName in newList
                where !currentList.Contains(i => i.Name = itemName)
                select new T {
                    Name = itemName,
                    Project = project
                };


    //find items to delete
    var toDelete = from item in currentList
                   where !newList.Contains(item.Name)
                   select item;

    toAdd.ToList().ForEach(item => item.Save());
    toDelete.ToList().ForEach(item => item.Delete());
}

类别和优先级必须派生自IStoreableItem,该接口包含名称、项目以及保存/删除方法。


0

你需要让Priority和Category从同一个基类派生出来...然后你可以按照以下方式进行操作:

public void ProcessLists<ItemType>(Func<int, IEnumerable<ItemType>> Loader) whereItemType : CommonBase, new() {
List<string> items = new List<string>();
IList<ItemType> currentItems = Loader(project.ID).ToList();
if (currentItems != null) {
    foreach (var existingItem in currentItems) {
        if (items.Contains(existingItem.Name))
            items.Remove(existingItem.Name);
        else
            existingItem.Delete(Services.UserServices.User);
    }
    foreach (string item in items) {
        ItemType item = new ItemType();
        item.Project = project
        item.Name = item.ToString();
        item.Save();
    }
}

}

当然,有些类型(如project.ID)只是猜测的,应该用正确的行替换。

您可以这样调用优先级函数:

ProcessLists<Priority>(id => Priority.LoadForProject(project.ID));

通过这种解决方案,当您需要调用新的ItemType时会遇到困难,因为它需要根据实际类型进行区分。这里应该采用泛型的方式。 - Brian Genisio
@Brian:嗯,恐怕我没有看到你的观点。你能进一步解释一下吗? - Christian
@Christian:在他的两个不同实现中,他调用了new Category(args)和new Project(args)。你不能只调用new ItemType()。你得不到你期望的类型。你需要将该方法泛型化,并调用Activator.Create(typeof(T), args)来创建正确的ItemType派生类。 - Brian Genisio
@Brian:当然,我会得到我所期望和需要的类型;)这就是函数签名中new()约束存在的原因。唯一的“问题”是我不能直接传递构造函数参数,而是通过属性进行赋值。顺便说一下...我的方法是通用的;) - Christian
我可以如何使用Activator.CreateInstance和dynamic吗?这是可能的吗? - learning
显示剩余3条评论

0
如果PriorityCategory都是从同一个基类派生出来的,具有共同的方法/属性,或者实现了相同的接口,那么是可以的。您只需要用该基类或接口(适用的)替换对PriorityCategory的特定引用即可。
有一些小的代码差异(比如第一个代码块中的List<string>(Categories)),您需要考虑如何处理,但大部分代码在祖先/接口问题解决后就会自然而然地落实到位。

我认为这不适合使用继承,因为类层次结构没有意义。你可以创建一个任意的基类,但它不符合作为子类的共同祖先的精神。在这里,使用接口更加合理,以提供一个共同的“API”给不同的类。 - Lazarus

0

我发现dynamic对于一组公开相同属性但不实现相同接口的类型非常有用。

使用foreach(dynamic d in myList)... d.Name...遍历列表,将其包装成一个方法,并传递不同的IList<object>实例(类别或优先级)。

需要C# 4.0。


1
这让我感觉像是作弊了 ;-) - Jouke van der Maas
与 Jouke 相同。这只是懒惰的表现。动态并不意味着您不需要定义接口。 - cRichter
非常感谢您的回复。我是C#的新手。您能否给我提供使用dynamic的代码片段?谢谢。 - learning

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接