【问题标题】:How to get a List of Base class objects, from x number of child classes lists?如何从 x 个子类列表中获取基类对象列表?
【发布时间】:2017-06-07 10:34:04
【问题描述】:

为了便于说明,我举了一个例子

public class Base {
    public string Name { get; set; }
    public string Surname { get; set; }

    public Base(string name, string surname) {
        Name = name;
        Surname = surname;
    }
}

public class Student : Base {

    public int StudentID { get; set; }

    public Student(string name, string surname, int studentID) 
                          : base(name, surname) {
        Name = name;
        Surname = surname;
        StudentID = studentID;
    }

    public override string ToString() {
        return string.Format("Name is {0}, surname is {1}, ID is {2}",
                              Name, Surname, StudentID);
    }
}

public class Teacher : Base {

    public string TeachingSubject { get; set; }

    public Teacher(string name, string surname, string teachingSubject)
                          : base(name, surname) {
        Name = name;
        Surname = surname;
        TeachingSubject = teachingSubject;
    }

    public override string ToString() {
        return string.Format("Name is {0}, surname is {1}, TeachingSubject is {2}",
                              Name, Surname, TeachingSubject);
    }
}

在我的例子中,有 4 个子类,但 2 个就足以保持简单并且仍然切中要害。请记住,Student 和 Teacher 类包含自己的属性,因此根据派生类属性,Base 列表中的每个对象都应该不同。

在项目中的某个地方我需要一个返回基本对象列表的方法

List<Base> GetAllLists() {

    return allLists; //Lets name the return list of Base objects like this
}

所以我需要将派生类中的对象列表放到基类列表中并在某处使用它。

我已经做过类似的事情了

List<Base> GetAllLists() {

    var allLists = new List<Base>();

    var studentList = new StudentService().GetList();  //Service class that populates the list of students from a csv file
    foreach (var item in studentList) {
        allLists.Add(new Student(item.Name, item.Surname, item.StudentID));
    }
    var teacherList = new TeacherService().GetList();  //Service class that populates the list of teachers from a csv file
    foreach (var item in teacherList) {
        allLists.Add(new Teacher(item.Name, item.Surname, item.TeachingSubject));
    }

    return allLists;
}

我收到一条评论:

没有必要遍历所有列表并实例化一个新的 每个实体的对象,因为它们都继承自同一个基础 类

那么如何通过其他方式实现呢?

【问题讨论】:

  • 你不需要写Name = name; Surname = surname;,因为你已经有: base(name, surname)

标签: c# list inheritance


【解决方案1】:

我猜AddRange 方法可以让你一次性添加所有对象:

var allLists = new List<Base>();

var studentList = new StudentService().GetList();  

allLists.AddRange(studentList);

var teacherList = new TeacherService().GetList();  

allLists.AddRange(teacherList);

由于StudentTeacher 从基类继承,它们正好可以放入基类列表中。

这里最大的不同是你只添加了引用!这意味着如果您更改原始 studentList 中的值,它也会在 allLists 列表中更改!

而在您发布的代码中,这不会发生,因为您为 studentList 中的每个条目创建了一个新实例。这样您就可以在两个列表之间创建独立性。

但如果studentList 只是一个未在其他地方操作的局部变量,则使用AddRange 并简单地添加对allLists 列表的引用是合法的

这是一个小测试程序。将其复制粘贴到控制台应用程序中,然后自己查看差异。只需更改 copyreference 值并比较结果:

void Main()
{       
    bool copyreference = true;

var allLists = new List<Base>();

    var studentList  = new List<Student>();   
    studentList.Add(new Student("Alf", "Bedrock", 123));
    studentList.Add(new Student("Alfine", "Bedrock", 456)); 

    var teacherList  = new List<Teacher>();    
    teacherList.Add(new Teacher("Brad", "Gulp", "MATH"));
    teacherList.Add(new Teacher("Evelyn", "Gulp", "BIO"));


    if (copyreference)
    {
        allLists.AddRange(studentList);
        allLists.AddRange(teacherList);
    }
    else
    {
        foreach (var item in studentList)
        {
            allLists.Add(new Student(item.Name, item.Surname, item.StudentID));
        }
    }

    Console.WriteLine(String.Join(Environment.NewLine, allLists));
    // TEST changing a value in the original list
    studentList[0].Name = "Harry";
    // if you copied references you will see the change in the final list
    Console.WriteLine(Environment.NewLine + String.Join(Environment.NewLine, allLists));
}

【讨论】:

  • 你测试过这个吗?由于List&lt;Student&gt;List&lt;Base&gt; 不同,我认为不允许以这种方式使用AddRange
  • @Maarten 实际上是来自经验。但为了完整起见,我会发布一个可测试的代码示例
  • @Maarten - 这是完全有效的代码,AddRange 方法将允许您添加 List&lt;Student&gt;List&lt;Teacher&gt;,只要您添加的类型范围 all 继承您要添加到的列表的基类。
  • @MongZhu - 同意。就个人而言,我更喜欢使用var 来减少代码,只要它首先完全清楚var 创建的类型是什么;否则,为了清楚起见,我在声明中显示了预期的类型。
  • @MongZhu AddRange() 对我和我的问题来说就像一个魅力。感谢您花时间解决它并为您的体验欢呼:)
【解决方案2】:

如果在调用GetAllLists 方法时允许创建列表的新实例,我会执行以下操作

public List<Base> GetAllLists() {
    var studentList = new StudentService().GetList();  
    var teacherList = new TeacherService().GetList();  

    return studentList.Cast<Base>()
        .Concat(teacherList.Cast<Base>())
        .ToList();
}

【讨论】:

  • 虽然这是使用Cast&lt;T&gt;() 方法的好方法,但我不喜欢用它来解决这个问题。我更喜欢向List&lt;T&gt; 添加范围,只要范围都继承自T;它更具可读性。
  • 想象一下,您需要将 10 个子类放入一个父列表中。我需要从 4 个子类中列出一个列表,并且不想使用 Concat 方法。此外,即使只有 2 个子类,使用 AddRange 的代码也会更简洁。
【解决方案3】:
  1. 你不需要写Name = name; Surname = surname;,因为你已经有: base(name, surname)

  2. 关于AddRange()中的shadow/deep copy,你也可以实现ICloneable或者添加一个拷贝构造函数。示例:

类定义:

public class Base {
    public string Name { get; set; }
    public string Surname { get; set; }

    public Base(string name, string surname) {
        Name = name;
        Surname = surname;
    }
}

public class Student : Base, ICloneable {  // ICloneable

    public int StudentID { get; set; }

    public Student(string name, string surname, int studentID) : base(name, surname) {
        //Name = name;
        //Surname = surname;
        StudentID = studentID;
    }

    public override string ToString() {
        return string.Format("Name is {0}, surname is {1}, ID is {2}", Name, Surname, StudentID);
    }

    public object Clone() {
        return MemberwiseClone();
    }
}

public class Teacher : Base {  // Copy constructor

    public string TeachingSubject { get; set; }

    public Teacher(string name, string surname, string teachingSubject) : base(name, surname) {
        //Name = name;
        //Surname = surname;
        TeachingSubject = teachingSubject;
    }

    public override string ToString() {
        return string.Format("Name is {0}, surname is {1}, TeachingSubject is {2}", Name, Surname, TeachingSubject);
    }

    public Teacher(Teacher obj) : base(obj.Name, obj.Surname) {
        TeachingSubject = obj.TeachingSubject;
    }
}

示例用法:

// AddRange()
List<Student> stud = new List<Student>(2);
stud.Add(new Student("s1", "ss1", 1));
stud.Add(new Student("s2", "ss2", 2));
List<Teacher> teac = new List<Teacher>(2);
teac.Add(new Teacher("t1", "ts1", "subject1"));
teac.Add(new Teacher("t2", "ts2", "subject2"));

List<Base> bas = new List<Base>(4);
bas.AddRange(stud);
bas.AddRange(teac);
Debug.Print($"{Environment.NewLine}AddRange():");
Debug.Print($"Base List:{Environment.NewLine}{bas[0]}{Environment.NewLine}{bas[1]}{Environment.NewLine}{bas[2]}{Environment.NewLine}{bas[3]}");

stud[0] = new Student("s3_changed", "ss3_changed", 3);
stud[1].Name = "s4_changed";
stud[1].Surname = "ss4_changed";
stud[1].StudentID = 4;
bas[2].Name = "b3_changed";
bas[2].Surname = "bs3_changed";
((Teacher)bas[2]).TeachingSubject = "bsub3_changed";
bas[3] = new Teacher("t4_changed", "ts4_changed", "tsbu4_changed");
Debug.Print($"{Environment.NewLine}After Change:");
Debug.Print($"Student List:{Environment.NewLine}{stud[0]}{Environment.NewLine}{stud[1]}");
Debug.Print($"Teacher List:{Environment.NewLine}{teac[0]}{Environment.NewLine}{teac[1]}");
Debug.Print($"Base List:{Environment.NewLine}{bas[0]}{Environment.NewLine}{bas[1]}{Environment.NewLine}{bas[2]}{Environment.NewLine}{bas[3]}");

// Concat()
List<Student> stud2 = new List<Student>(2);
stud2.Add(new Student("s1", "ss1", 1));
stud2.Add(new Student("s2", "ss2", 2));
List<Teacher> teac2 = new List<Teacher>(2);
teac2.Add(new Teacher("t1", "ts1", "subject1"));
teac2.Add(new Teacher("t2", "ts2", "subject2"));

List<Base> bas2 = new List<Base>();
bas2 = bas2.Concat(stud2).Concat(teac2).ToList();
Debug.Print($"{Environment.NewLine}Concat:");
Debug.Print($"Base List:{Environment.NewLine}{bas2[0]}{Environment.NewLine}{bas2[1]}{Environment.NewLine}{bas2[2]}{Environment.NewLine}{bas2[3]}");

stud2[0] = new Student("s3_changed", "ss3_changed", 3);
stud2[1].Name = "s4_changed";
stud2[1].Surname = "ss4_changed";
stud2[1].StudentID = 4;
bas2[2].Name = "b3_changed";
bas2[2].Surname = "bs3_changed";
((Teacher)bas2[2]).TeachingSubject = "bsub3_changed";
bas2[3] = new Teacher("t4_changed", "ts4_changed", "tsbu4_changed");
Debug.Print($"{Environment.NewLine}After Change:");
Debug.Print($"Student List:{Environment.NewLine}{stud2[0]}{Environment.NewLine}{stud2[1]}");
Debug.Print($"Teacher List:{Environment.NewLine}{teac2[0]}{Environment.NewLine}{teac2[1]}");
Debug.Print($"Base List:{Environment.NewLine}{bas2[0]}{Environment.NewLine}{bas2[1]}{Environment.NewLine}{bas2[2]}{Environment.NewLine}{bas2[3]}");

// AddRange() clone
List<Student> stud3 = new List<Student>(2);
stud3.Add(new Student("s1", "ss1", 1));
stud3.Add(new Student("s2", "ss2", 2));
List<Teacher> teac3 = new List<Teacher>(2);
teac3.Add(new Teacher("t1", "ts1", "subject1"));
teac3.Add(new Teacher("t2", "ts2", "subject2"));

List<Base> bas3 = new List<Base>(4);
bas3.AddRange(stud3.Select(x => (Student)x.Clone()).ToList());  // Clone
bas3.AddRange(teac3.Select(x => new Teacher(x)).ToList());  // Copy c'tor
Debug.Print($"{Environment.NewLine}AddRange() clone:");
Debug.Print($"Base List:{Environment.NewLine}{bas3[0]}{Environment.NewLine}{bas3[1]}{Environment.NewLine}{bas3[2]}{Environment.NewLine}{bas3[3]}");

stud3[0] = new Student("s3_changed", "ss3_changed", 3);
stud3[1].Name = "s4_changed";
stud3[1].Surname = "ss4_changed";
stud3[1].StudentID = 4;
bas3[2].Name = "b3_changed";
bas3[2].Surname = "bs3_changed";
((Teacher)bas3[2]).TeachingSubject = "bsub3_changed";
bas3[3] = new Teacher("t4_changed", "ts4_changed", "tsbu4_changed");
Debug.Print($"{Environment.NewLine}After Change:");
Debug.Print($"Student List:{Environment.NewLine}{stud3[0]}{Environment.NewLine}{stud3[1]}");
Debug.Print($"Teacher List:{Environment.NewLine}{teac3[0]}{Environment.NewLine}{teac3[1]}");
Debug.Print($"Base List:{Environment.NewLine}{bas3[0]}{Environment.NewLine}{bas3[1]}{Environment.NewLine}{bas3[2]}{Environment.NewLine}{bas3[3]}");

输出:

AddRange():
Base List:
Name is s1, surname is ss1, ID is 1
Name is s2, surname is ss2, ID is 2
Name is t1, surname is ts1, TeachingSubject is subject1
Name is t2, surname is ts2, TeachingSubject is subject2

After Change:
Student List:
Name is s3_changed, surname is ss3_changed, ID is 3
Name is s4_changed, surname is ss4_changed, ID is 4
Teacher List:
Name is b3_changed, surname is bs3_changed, TeachingSubject is bsub3_changed
Name is t2, surname is ts2, TeachingSubject is subject2
Base List:
Name is s1, surname is ss1, ID is 1
Name is s4_changed, surname is ss4_changed, ID is 4
Name is b3_changed, surname is bs3_changed, TeachingSubject is bsub3_changed
Name is t4_changed, surname is ts4_changed, TeachingSubject is tsbu4_changed

Concat:
Base List:
Name is s1, surname is ss1, ID is 1
Name is s2, surname is ss2, ID is 2
Name is t1, surname is ts1, TeachingSubject is subject1
Name is t2, surname is ts2, TeachingSubject is subject2

After Change:
Student List:
Name is s3_changed, surname is ss3_changed, ID is 3
Name is s4_changed, surname is ss4_changed, ID is 4
Teacher List:
Name is b3_changed, surname is bs3_changed, TeachingSubject is bsub3_changed
Name is t2, surname is ts2, TeachingSubject is subject2
Base List:
Name is s1, surname is ss1, ID is 1
Name is s4_changed, surname is ss4_changed, ID is 4
Name is b3_changed, surname is bs3_changed, TeachingSubject is bsub3_changed
Name is t4_changed, surname is ts4_changed, TeachingSubject is tsbu4_changed

AddRange() clone:
Base List:
Name is s1, surname is ss1, ID is 1
Name is s2, surname is ss2, ID is 2
Name is t1, surname is ts1, TeachingSubject is subject1
Name is t2, surname is ts2, TeachingSubject is subject2

After Change:
Student List:
Name is s3_changed, surname is ss3_changed, ID is 3
Name is s4_changed, surname is ss4_changed, ID is 4
Teacher List:
Name is t1, surname is ts1, TeachingSubject is subject1
Name is t2, surname is ts2, TeachingSubject is subject2
Base List:
Name is s1, surname is ss1, ID is 1
Name is s2, surname is ss2, ID is 2
Name is b3_changed, surname is bs3_changed, TeachingSubject is bsub3_changed
Name is t4_changed, surname is ts4_changed, TeachingSubject is tsbu4_changed

【讨论】:

  • 潮顾,感谢您用耗时的例子彻底回答,我相信它会帮助其他人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
相关资源
最近更新 更多