Generic class where T : Class clarification - c#

There are semi answer to this question which I have read through thoroughly, as well as all things MSDN about generic classes but I am still having trouble when a generic class inherits from another class: where T: ClassName
For example, here is my generic list class
public class MyGenericList2<T> where T : Person
{
private T[] list;
public MyGenericList2(int size)
{
list = new T[size];
}
public T getItem(int index)
{
T temp = default(T);
temp = list[index];
return temp;
}
public void setItem(int index, T value)
{
list[index] = value;
}
public void DisplayList()
{
for (int i = 0; i < list.Length; i++)
{
Console.Out.WriteLine(list[i]);
}
}
}
It inherits from the person class:
NOTE: It is shortened for clarity sake
public abstract class Person
{
protected string firstName;
// Getters
public string getFirstName()
{
return this.firstName;
}
public void setFirstName(string fname)
{
this.firstName = fname;
}
}
When I try to call it I get an error about trying to convert a string to a {namespace}.Person which I sort of get, in that I am trying to put a string into a 'Person' box, but how does one call the class using this mechanism?
Here is the main method
public static void Main(string[] args)
{
MyGenericList2<Person> studentGeneric = new MyGenericList2<Person>(3);
Student st1 = new Student();
st1.setFirstName("Thor");
studentGeneric.setItem(0, st1); //This does not work
studentGeneric.setItem(1, Person.setFirstName("Odin"); // Does not work
studentGeneric.setItem(2, st1.setFirstName("Slepnir"); // Does not work
studentGeneric.DisplayList();
Console.ReadLine();
}
If I cut out the Where T : Person and use GenericList2<string> it works fine, which makes sense since it is string to string.
Any help would be appreciated
quick clarification Student inherits from Person:
public class Student : Person
{
// Student 1
private string studentID01 = "001";
public string getStudentID01()
{
return this.studentID01;
}
}

First of all I would recommend using public properties for your classes, for example:
public abstract class Person
{
public string FirstName { get; set; }
}
public class Student : Person
{
public string StudentId { get; set; }
}
This means your list code would work like this:
Student st1 = new Student();
st1.FirstName = "Thor";
studentGeneric.setItem(0, st1);
And you can even use this syntax:
studentGeneric.setItem(1, new Student
{
FirstName = "Odin"
});
Additionally, the .Net Framework already provides a really nice set of generic collection classes you can use so you don't really need your MyGenericList2<T> class. For example, the most commonly used class is System.Collections.Generic.List:
var people = new System.Collections.Generic.List<Person>();
people.Add(new Student
{
FirstName = "Odin"
});
Or even using the collection initialiser syntax:
var people = new System.Collections.Generic.List<Person>
{
new Student
{
FirstName = "Odin"
}
});
Finally, the problem you are having with outputting your values to the console is because C# doesn't know what to do with your class so by default outputs the value of student.ToString(). And becaue you haven't told your class what to do with it, it just outputs the name of the type. You can either override ToString or, much simpler just call the getFirstName() method:
Console.WriteLine(list[i].getFirstName());

You are using setItem incorrectly. This method can be used to set the value of elements in the list array in an instance of MyGenericList2 class.
To use the setFirstName method on an instance of the Student class, first use getItem to return the object instance. For example:
public void Main(string[] args)
{
MyGenericList2<Person> studentGeneric = new MyGenericList2<Person>(3);
Student st1 = new Student();
st1.setFirstName("Thor");
studentGeneric.setItem(0, st1);
Student st2 = new Student();
studentGeneric.setItem(1, st2);
studentGeneric.getItem(1).setFirstName("Odin");
Student st3 = new Student();
studentGeneric.setItem(2, st3);
studentGeneric.getItem(2).setFirstName("Slepnir");
studentGeneric.DisplayList();
Console.ReadLine();
}
To display the list contents correctly, replace your DisplayList() method with:
public void DisplayList()
{
for (int i = 0; i < list.Length; i++)
{
if(list[i] != null){
Console.Out.WriteLine("{0}: {1}", i, list[i].getFirstName());
}
else
{
Console.Out.WriteLine("{0}: [NULL]", i);
}
}
}

Related

How to access an array of objects from another class

I'm trying to loop through an array of objects and print their properties from a different class.
My main class is
class Program
{
static void Main()
{
//This to be change to relative path
string Path = #"C:\Users\";
string[] lines = { }; ;
//Reading file
if (File.Exists(Path))
{
lines = File.ReadAllLines(Path);
StudentReport.ReadStudents(lines);
}
else
{
Console.WriteLine("The file does't exist");
}
//Printing Students
PrintStudent.Print(lines.Length);
}
}
I'm using this code to declare the array
public class StudentReport
{
public static void ReadStudents(string[] Lines)
{
//declare an array with the number of students
Student[] studentArray = new Student[Lines.Length];
int StudentCounter = 0;
foreach (string Line in Lines)
{
String[] Student = Line.Split(',');
//Calculating values
string ID = Student[0].PadLeft(10, '0');
string name = Student[1];
//Initialize the object
studentArray[StudentCounter] = new Student
{
FullName = name,
ID = ID,
};
StudentCounter++;
}
}
}
And I'm using this class to construct my student object
class Student
{
public string FullName { get; set; }
public string ID { get; set; }
}
To output the student object properties, I made another class. The problem is that I couldn't access the value of the objects array from my new class.
The class I made for outputting purposes is the following, but I cannot get the values. The error is 'Student does not contain a definition for student array
public class PrintStudent
{
public static void Print(int StudentCounter)
{
for(int i = 0; i > StudentCounter; i++)
{
Console.WriteLine(Student.studentArray[i].FullName);
}
}
}
Your error is Student does not contain a definition for studentArray. This is because your Student class does not have studentArray, only the properties FullName and ID. So accessing Student.studentArray[i] doesn't make sense.
Probably what you want is for ReadStudents to return the studentArray so it doesn't go out of scope by changing the method signature to return the Student[], and calling return studentArray at the end.
Then, you can pass your studentArray to your PrintStudent.Print method in the parameters.
By the way, the for(int i = 0; i > StudentCounter; i++) has a wrong < and will never run (lines.Length which is the StudentCounter will always be >= 0)
You can use studentArray.Length, or a foreach loop to iterate over this array, rather than pass the StudentCounter.

ArrayList Conceptual Issues

I have tried to add 2 types of objects in an Array List and then have tried to display them but somehow it is not working. Do I need to use 2 Array List objects or how is it going to work?
Error Message:
Unable to cast object of type 'ArrayList_Practice.Student' to type
'ArrayList_Practice.Employees'.
class Program
{
static void Main(string[] args)
{
Student st=null;
Employees emp = null;
ArrayList al = new ArrayList();
Console.WriteLine("Enter Records");
for (int i = 0; i < 2; i++)
{
st = new Student();
Console.WriteLine("Enter roll");
st.roll = int.Parse(Console.ReadLine());
Console.WriteLine("Enter name");
st.name = Console.ReadLine();
Console.WriteLine("Enter course");
st.course = Console.ReadLine();
al.Add(st);
emp = new Employees();
Console.WriteLine("Enter empID");
emp.empID = int.Parse(Console.ReadLine());
Console.WriteLine("Enter name");
emp.name = Console.ReadLine();
al.Add(emp);
}
Console.WriteLine("/////////////Show Records//////////");
for (int i = 0; i < 2; i++)
{
Console.WriteLine("Roll "+((Student)al[i]).roll.ToString());
Console.WriteLine("Name "+((Student)al[i]).name);
Console.WriteLine("Course "+((Student)al[i]).course);
Console.WriteLine("EmpID "+((Employees)al[i]).empID.ToString());
Console.WriteLine("EmpName "+((Employees)al[i]).name);
}
Console.ReadKey();
}
}
class Student
{
public int roll{ get; set;};
public string name{ get; set;};
public string course{ get; set;};
}
class Employees
{
public int empID{ get; set;};
public string name{ get; set;};
}
}
Your first element is a Student, and you are trying to cast it to Employee on first iteration in the loop.That's why you are getting an InvalidCastException in run-time.Don't use ArrayLists, use strongly-typed generic collections instead.For ex: List<T>.
If you want to display common properties and you want to store Students and Employees into the same list, you can create a common interface for them and implement it.Then you can have a List<CommonInterface> and store your instances.But if you have different properties (it seems you have) you can't access them using common interface or base class,instead you can simply create an extension method and use Reflection to display all property values like this:
public static class Extensions
{
public static string DisplayPerson<T>(this T source)
{
if(source == null) throw new ArgumentNullException("source");
var flags = BindingFlags.Instance | BindingFlags.Public;
var properties = source.GetType().GetProperties(flags);
if (properties.Any())
{
StringBuilder sb = new StringBuilder();
foreach (var prop in properties)
{
sb.AppendFormat("{0} : {1}", prop.Name, prop.GetValue(source));
sb.AppendLine();
}
return sb.ToString();
}
return string.Empty;
}
}
Then just call it from the loop:
for (int i = 0; i < al.Count; i++)
{
Console.WriteLine(al[i].DisplayPerson());
}
Edit: Another way using common interface
public interface IPerson
{
string Name { get; set; }
int Id { get; set; }
}
class Student : IPerson
{
/* implement the properties */
}
class Employees : IPerson
{
/* implement the properties */
}
static void Main(string[] args)
{
List<IPerson> personList = new List<IPerson>();
personList.Add(new Student {/* set properties */});
personList.Add(new Employee {/* set properties */});
// use a loop and display your properties without casting
}
in your array list, your 0th element is a student type and 1st element is an employee type.
in your loop, then you're trying to cast your 0th element to an employee to display empID.
hence you need to be aware of this..
you need to do proper cast check for the loop to work.
check if the element is student or employee and display accordingly.
for (int i = 0; i < al.Count; i++)
{
var student = al[i] as Student;
if (student != null)
{
Console.WriteLine("Roll "+ student.roll.ToString());
Console.WriteLine("Name "+ student.name);
Console.WriteLine("Course "+ student.course);
}
else
{
var employee = al[i] as Employee;
if (employee != null)
{
Console.WriteLine("EmpID "+ employee.empID.ToString());
Console.WriteLine("EmpName "+ employee.name);
}
}
}
though this works for your problem, in general you should be using strongly types collections.
e.g. List<Student> and List<Employee>
You need two lists, because you are trying to store two different types of objects in the array. While it's possible to manage this, you'd be better off keeping it simple: have an array for students and an array for employees.
I'm not sure why you enter the student and employee records at the same time, in pairs. Are the employees connected to the students in some way? Are the students employees? If so, you'd be better off creating a single object that represents student-employee, or whatever the relationship is, and filling a single list with items of that type.
Also, it's 2014 and you shouldn't be using ArrayList. At the very least, use List<>.

C# List Elements in the Session Not Responding

I'm doing a simple program to add a student(with ID,Name) to a List, then to search Student by ID through session.
Add Student Module is like below,
protected void addStudent(object sender, EventArgs e)
{
List<Student> thisstdlist = new List<Student>();
thisstdlist = (List<Student>)Session["stdlist"];
thisstdlist.Add(new Student(txtsid.Text,txtsname.Text));
Session["stdlist"] = thisstdlist;
Response.Redirect("Home.aspx");
}
Search Student Module is Like Below,
protected void searchStudent(object sender, EventArgs e)
{
foreach (Student element in (List<Student>)Session["stdlist"])
{
if(element.getID().Equals(txtstdid.Text)){
txtstdname.Text = element.getName();
}
}
}
Student Class is like below,
public class Student
{
private String Name;
private String ID;
public Student(String sid, String sn) {
this.Name = sn;
this.ID = sid;
}
public String getName() {
return this.Name;
}
public String getID()
{
return this.ID;
}
}
But when I added students, for ex: 100,John and Search by 100 it gives me no result. Please can anyone show me the mistake or the correct way of doing this.
are you setting breakpoints and actually checking what the values of these lists and what is actually stored in the session?
.Equals() is not doing what you think it is
try :
foreach (Student element in (List<Student>)Session["stdlist"])
{
if(element.ID == txtstdid.Text){
txtstdname.Text = element.getName();
}
}
The add Student module won't initialize the student list correctly - you are creating a new List<Student> and then throwing the new list away with the next line assignment. I would go with something like:
var thisstdlist = (List<Student>)Session["stdlist"];
// If a key isn't found in Session, it will be null ..
if (thisstdlist == null)
{
// i.e. only re-initialize if it was missing from session
thisstdlist = new List<Student>();
// You don't need to continually reassign the session variable
Session["stdlist"] = thisstdlist;
}
// Adds to the list; now that Session also has a reference to the same list
thisstdlist.Add(new Student(txtsid.Text,txtsname.Text));
As per the comment, note that c# has automatic (albeit mutable) properties - you don't need the Java-style getters and setters.
public class Student
{
public Student(string sid, string sn)
{
Name = sn;
ID = sid;
}
public string Name
{
get;
set;
}
public string ID
{
get;
set;
}
}
Also, in .Net, == for strings is overridden to test values (unlike Java's reference equality for strings), so you can rewrite the comparison as:
if (element.ID == txtstdid.Text)
{
txtstdname.Text = element.Name;
}
Re : foreach - I guess means that you are using the List in a Dictionary (HashMap) fashion - if you use Dictionary instead of List - this will allow you do remove the foreach in favour of:
// addStudent ...
var txtstdname = new Dictionary<string, Student>();
// ...
txtstdname.Add(txtsid.Text, new Student(txtsid.Text,txtsname.Text))
// searchStudent ...
Student element = null;
if (txtstdname.TryGetValue(out element))
{
txtstdname.Text = element.Name();
}

Using DefaultIfEmpty with an object?

I saw an example on MSDN where it would let you specify the default value if nothing is returned. See below:
List<int> months = new List<int> { };
int firstMonth2 = months.DefaultIfEmpty(1).First();
Is it possible to use this functionality with an object? Example:
class object
{
int id;
string name;
}
code:
List<myObjec> objs = new List<myObjec> {};
string defaultName = objs.DefaultIfEmpty(/*something to define object in here*/).name;
UPDATE:
I was thinking I could do something like this:
List<myObjec> objs = new List<myObjec> {};
string defaultName = objs.DefaultIfEmpty(new myObjec(-1,"test")).name;
But haven't been able to. It should be noted that I am actually trying to use this method on an object defined in my DBML using LINQ-To-SQL. Not sure if that makes a difference in this case or not.
You need to pass an instantiated class as a parameter of the DefaultIfEmpty.
class Program
{
static void Main(string[] args)
{
var lTest = new List<Test>();
var s = lTest.DefaultIfEmpty(new Test() { i = 1, name = "testing" }).First().name;
Console.WriteLine(s);
Console.ReadLine();
}
}
public class Test
{
public int i { get; set; }
public string name { get; set; }
}
To add to it and make it a bit more elegant (IMO) add a default constructor:
class Program
{
static void Main(string[] args)
{
var lTest = new List<Test>();
var s = lTest.DefaultIfEmpty(new Test()).First().name;
Console.WriteLine(s);
Console.ReadLine();
}
}
public class Test
{
public int i { get; set; }
public string name { get; set; }
public Test() { i = 2; name = "testing2"; }
}
As per the MSDN page on this Extension Method you can do what you want:
http://msdn.microsoft.com/en-us/library/bb355419.aspx
Check the sample on this page for an example on how to use this with an object.
i must admit i am not too sure i understand your question, but i'll try to suggest using double question mark if the returned object might be null. Like so:
myList.FirstOrDefault() ?? new myObject();
You can create a default Object Like this:
Object o_Obj_Default = new Object();
o_Obj_Default.id = 3;
o_Obj_Default.name = "C";
And add it to your default value :
string defaultName = objs.DefaultIfEmpty(o_Obj_Default).First().name;
If your list "objs" is empty, the result will be "C"

How to make IEnumerable<T> readonly?

Why are the lists list1Instance and p in the Main method of the below code pointing to the same collection?
class Person
{
public string FirstName = string.Empty;
public string LastName = string.Empty;
public Person(string firstName, string lastName) {
this.FirstName = firstName;
this.LastName = lastName;
}
}
class List1
{
public List<Person> l1 = new List<Person>();
public List1()
{
l1.Add(new Person("f1","l1"));
l1.Add(new Person("f2", "l2"));
l1.Add(new Person("f3", "l3"));
l1.Add(new Person("f4", "l4"));
l1.Add(new Person("f5", "l5"));
}
public IEnumerable<Person> Get()
{
foreach (Person p in l1)
{
yield return p;
}
//return l1.AsReadOnly();
}
}
class Program
{
static void Main(string[] args)
{
List1 list1Instance = new List1();
List<Person> p = new List<Person>(list1Instance.Get());
UpdatePersons(p);
bool sameFirstName = (list1Instance.l1[0].FirstName == p[0].FirstName);
}
private static void UpdatePersons(List<Person> list)
{
list[0].FirstName = "uf1";
}
}
Can we change this behavior with out changing the return type of List1.Get()?
Thanks
In fact, IEnumerable<T> is already readonly. It means you cannot replace any items in the underlying collection with different items. That is, you cannot alter the references to the Person objects that are held in the collection. The type Person is not read only, however, and since it's a reference type (i.e. a class), you can alter its members through the reference.
There are two solutions:
Use a struct as the return type (that makes a copy of the value each time it's returned, so the original value will not be altered — which can be costly, by the way)
Use read only properties on the Person type to accomplish this task.
Return a new instance of Person that is a copy of p instead of p itself in Get(). You'll need a method to make a deep-copy of a Person object to do this. This won't make them read only, but they will be different than those in the original list.
public IEnumerable<Person> Get()
{
foreach (Person p in l1)
{
yield return p.Clone();
}
}
They aren't pointing to the same .Net collection, but rather, to the same Person objects. The line:
List<Person> p = new List<Person>(list1Instance.Get());
copies all the Person elements from list1Instance.Get() to list p. The word "copies" here means copies the references. So, your list and IEnumerable just happen to point to the same Person objects.
IEnumerable<T> is always readonly, by definition. However, the objects inside may be mutable, as in this case.
First of all, your List in your class is public, so there's nothing stopping anyone from directly accessing the list itself.
Secondly, I would implement IEnumerable and return this in my GetEnumerator Method
return l1.AsReadOnly().GetEnumerator();
You could make a deepclone of each item in the list, and never return references to your original items.
public IEnumerable<Person> Get()
{
return l1
.Select(p => new Person(){
FirstName = p.FirstName,
LastName = p.LastName
});
}
If your person object is a real object then you should consider using an immutable version.
public class Person
{
public FirstName {get; private set;}
public LastName {get; private set;}
public Person(firstName, lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
In this way its not possible to change the content of the instance once created and therefore it isn't important that existing instances are reused in multiple lists.
IEnumerable<T> is readonly
p is a new collection which doesn't depend on list1instance.
The mistake you made, is that you thought that this line
list[0].FirstName = "uf1";
would only modify one of the lists, when on fact you're modifying the Person object.
The two collections are distinct, they just happen to have the same items.
To prove that they are different, try adding and removing items from one of the lists, and you'll see that the other one isn't affected.
This code returns a derived class, so as requested the return type hasn't changed.
It does throw an error if you try and change a field (via property) so is 'read only'. If you did want to be able to change values without affecting the original the clone answer above is better.
class Person
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public Person(string firstName, string lastName) {
this.FirstName = firstName;
this.LastName = lastName;
}
}
class PersonReadOnly : Person
{
public override string FirstName { get { return base.FirstName; } set { throw new Exception("setting a readonly field"); } }
public override string LastName { get { return base.LastName; } set { throw new Exception("setting a readonly field"); } }
public PersonReadOnly(string firstName, string lastName) : base(firstName, lastName)
{
}
public PersonReadOnly(Person p) : base(p.FirstName, p.LastName)
{
}
}
class List1
{
public List<Person> l1 = new List<Person>();
public List1()
{
l1.Add(new Person("f1", "l1"));
l1.Add(new Person("f2", "l2"));
l1.Add(new Person("f3", "l3"));
l1.Add(new Person("f4", "l4"));
l1.Add(new Person("f5", "l5"));
}
public IEnumerable<Person> Get()
{
foreach (Person p in l1)
{
yield return new PersonReadOnly(p);
}
//return l1.AsReadOnly();
}
}
class Program
{
static void Main(string[] args)
{
List1 list1Instance = new List1();
List<Person> p = new List<Person>(list1Instance.Get());
UpdatePersons(p);
bool sameFirstName = (list1Instance.l1[0].FirstName == p[0].FirstName);
}
private static void UpdatePersons(List<Person> list)
{
// readonly message thrown
list[0].FirstName = "uf1";
}

Categories

Resources