C# Polymorphism/Lists - c#

I've been having trouble with this. I'll summarise briefly.
I have 4 classes. One is a "Person" class. The other three are "Rental", with two classes inheriting from that, "RentalByDay" and "RentalByKM".
Within the "Person" class, there is a list of Rental objects. The issue I'm having is I'm not sure how to add to that list the Rental objects as they are created.
class Person
{
private string _FirstName;
private string _LastName;
public Person(string LastName, string FirstName)
{
_LastName = LastName;
_FirstName = FirstName;
}
public string LastName
{
get { return _LastName; }
}
public string FirstName
{
get { return _FirstName; }
}
public List<Rental> _rentedlist = new List<Rental>();
public ReadOnlyCollection<Rental> rentedlist
{
get { return _rentedlist.AsReadOnly();}
}
public void addrental(Rental Rental)
{
_rentedlist.Add(Rental);
}
public void PrintRentals()
{
foreach (Rental d in _rentedlist)
{
Console.WriteLine(d.Person);
}
}
}
class Rental
{
private double _rentduration;
Person _Person;
public double rentduration
{
get { return _rentduration; }
set { _rentduration = value; }
}
public Person Person
{
get { return _Person; }
set { _Person = value; }
}
public Rental(Person Person, double rentduration)
{
_Person = Person;
_rentduration = rentduration;
}
}
class RentalByDay : Rental
{
public RentalByDay(Person Person, double rentbydays)
: base(Person, rentbydays)
{
// add to rental list here?
}
}
class RentalByKm : Rental
{
public RentalByKm(Person Person, double rentbykm)
: base(Person, rentbykm)
{
// add to rental list here?
}
}
class RentalAgency
{
static void Main(string[] args)
{
Person jane = new Person("Bloggs", "Jane");
Person joe = new Person("Bloggs", "Joe");
Person peter = new Person("Piper", "Peter");
Person penny = new Person("Piper", "Penny");
new RentalByDay(jane, 5);
new RentalByDay(jane, 2);
jane.PrintRentals();
new RentalByDay(joe, 8);
new RentalByKm(joe, 15);
joe.PrintRentals();
new RentalByDay(peter, 1);
new RentalByKm(peter, 85);
peter.PrintRentals();
new RentalByDay(penny, 5);
new RentalByKm(penny, 42);
penny.PrintRentals();
Console.WriteLine("Quote for {0}", new RentalByDay(null, 10));
Console.WriteLine("Quote for {0}", new RentalByKm(null, 10));
}
}
The end result should be that when the Printrental is called, that all of the rentals for that person are displayed.
Any help would be appreciated. I feel like this is obvious, but for whatever reason I just cannot figure it out.
Thanks!

You need to create the rental, and then add the rental to the user, instead of trying to create the rental, and have the rental itself add itself to a user.
jane.addrental(new RentalByDay(jane, 5));
jane.addrental(new RentalByDay(jane, 2));
jane.PrintRentals();
joe.addrental(new RentalByDay(joe, 8));
joe.addrental(new RentalByKm(joe, 15));
joe.PrintRentals();
peter.addrental(new RentalByDay(peter, 1));
peter.addrental(new RentalByKm(peter, 85));
peter.PrintRentals();
penny.addrental(new RentalByDay(penny, 5));
penny.addrental( new RentalByKm(penny, 42));
penny.PrintRentals();
You could add the rental itself to the person, however, creating objects with new and not assigning them to anything is a bit unusual and confusing.
public Rental(Person Person, double rentduration)
{
_Person = Person;
_rentduration = rentduration;
_Person.addrental(this);
}
You may also want to add a ToStringoverride to your Person class, so when printed to the console, you get more than just "Person".
public override string ToString()
{
return string.Format("{0}, {1} - {2} rentals.", LastName, FirstName, _rentedlist.Count);
}

Look at this code
new RentalByDay(penny, 5);
new RentalByKm(penny, 42);
penny.PrintRentals();
You keep creating new instances of Rental subclasses, and are not associating those instances with the penny instance of Person.
You add a reference in your unnamed intance of RentalByKm to penny, but no reference from penny to Penny's rentals. When you print out the result, you again create a new object instance with no references to any other object.
You need to actually add those unnamed references to penny.
penny.addrental(new RentalByDay(penny, 5));
penny.addrental(new RentalByKm(penny, 42));
penny.PrintRentals();

public Rental(Person Person, double rentduration)
{
_Person = Person;
_rentduration = rentduration;
Person.addRental(this) // Add this in the base class Constructor. No need to duplicate in each specific
}

Related

How to do object construction for a class that uses the init syntax?

public class Person
{
private string _myName;
public Person(string myName)
{
_myName= myName;
}
public string Name => _myName;
}
My understanding of object construction use is like this: var obj = new Person("Tim");
An init-only setter assigns a value to the property or the indexer element only during object construction. Following is sample code using init:
public class Person
{
private string _myName;
public string Name
{
get => _myName;
init => _myName= value;
}
}
What is the way to construct an object for such a class so that the init is invoked during object construction? For example, it is:
var obj = new Person("Tim");
var obj = new Person().Name("Tim");
var obj = new Person(); obj.Name="Tim";
I cannot find how object is created in init examples from this msdn link: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init
The problem is that your class Person doesn't expose a parameterless constructor, the only constructor your class has is one that takes a string as a parameter, meaning you cannot call new Person(); you must supply a parameter of type string. Meaning, the only way to create a new Person in you current example is:
var person = new Person("Anders Hejlsberg");
And after that you cannot modify Name as the initialisation of the object is complete. If you want to change the Name property later, you'll have to remove the init keyword and use a normal setter. If you want to initialize your object like
var person = new Person
{
Name = "Denis Ritchie"
};
(Side note: This is because new Person { ... } implicitly calls a parameterless constructor, i.e it's equal to new Person() { ... }, you could also do new Person("Richard Stallman") { ... } if you wanted to enforce that a Person must always have a Name)
You'll have to give your class a parameterless constructor (or remove your one and only constructor), I'd also recommend using an auto property (i.e no explicit backing field). Re-writing your class like so would result in this:
public class Person
{
public string Name { get; init; }
}
// usage
var person = new Person
{
Name = "Erich Gamma"
};
If you want to enforce that object of type Person always have a Name set, but still want to use init properties for other properties, you can do that like so:
public class Person
{
public string Name { get; init; }
public string Nickname { get; init; }
public Person(string name)
{
Name = name;
}
}
// usage
var linus1 = new Person("Linus Torvalds");
var linus2 = new Person("Linus Sebastian")
{
Nickname = "Linus 'TechTips' Sebastian"
};
It would simply be
var myPerson = new Person() { Name = "Tim" };
or, you can even omit the parenthesis, this would be valid.
var myPerson = new Person { Name = "Tim" };
Also, you can greatly simplify your example, you don't need to define the attribute myName. You can simply use the auto-properties like that:
public class Person
{
public string Name { get; init; }
}

Generic class where T : Class clarification

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);
}
}
}

C# : Using struct , List and enum; format exception

I have enum called Department that has abbreviations. (e.g. CSIS, ECON etc.)
I have struct Course with fields name, number of the course(e.g. 101 Fundamentals Of Computing just the number)
I created the constructor with four parameters.
In Program cs I created the List of these courses. with the order of (name(which is string), Department(I got this through calling Department type in enum), CourseCode(enum members like CSIS)and credit hours).
I was required to print all the courses through toString().
public string Name
{
get { return name; }
set { name = value; }
}
public int Number
{
get { return number; }
set { number = value; }
}
public int CreditHours
{
get { return numOfCreditHours; }
set { numOfCreditHours = value; }
}
public override string ToString()
{
return String.Format("{ -30, 0}--{4, 1}/{2}--{3:0.0#}", name, Department, number, numOfCreditHours);
}
This is where I created my List: in Program cs.
static void Main(string[] args)
{
Course course = new Course(courses);
foreach (Course crs in courses)
{
Console.WriteLine(string.Join(" , ", crs));
}
Console.WriteLine();
//Array.Sort(courses);
Console.ReadLine();
}
private static List<Course> courses = new List<Course>()
{
new Course("Fundamentals of Programming", Department.CSIS, 1400, 4),
new Course("Intermediate Programming in C#", Department.CSIS,2530, 3),
new Course("Introduction to Marketing", Department.MKTG,1010, 3),
new Course("Algorithms and Data Structures", Department.CSIS,2420, 4),
new Course("Object Oriented Programming", Department.CSIS, 1410, 4)
};
I get format exception. I know why I get it. Because of string type. but some how I need to do it. Please help me with some explaining. and what I should do to get it right. Thanks.
it looks like you swapped places in string format:
return String.Format("{0,-30}--{1,4}/{2}---{3:0.0#}"
You're confusing indexes in the format string in "{ -30, 0}--{4, 1}/{2}--{3:0.0#}".
"{ -30, 0}" must be "{0, -30}".
"{4, 1}" must be "{1, 4}".
Then, returns must be:
return String.Format("{0,-30}--{1,4}/{2}--{3:0.0#}", name, Department, number, numOfCreditHours);

Can properties inside an object initializer reference each other?

Is it somehow possible for properties to reference each other during the creation of a dynamic object an anonymously-typed object (i.e. inside the object initializer)? My simplified example below needs to reuse the Age property without making a second heavy call to GetAgeFromSomewhere(). Of course it doesn't work. Any suggestion on how to accomplish this?
var profile = new {
Age = GetAgeFromSomewhere(id),
IsLegal = (Age>18)
};
Is something like this possible or not possible with dynamic objects anonymously-typed object initializers?
Unfortunately it's not possible, even with explicitly typed objects. This is because of the way object initializers work. For example:
public class MyClass
{
public int Age = 10;
public bool IsLegal = Age > 18;
}
Yields this compiler error at "IsLegal":
Error 1 A field initializer cannot reference the non-static field,
method, or property 'MyClass.Age' ...
Field initializer can't reference other non-static fields, and since anonymous types don't create static fields, you can't use the value of one field to initialize another. The only way around this, is to declare the variables outside the anonymous type and use them inside the initializer.
int age = GetAgeFromSomewhere(id);
var profile = new {
Age = age,
IsLegal = age > 18
};
Don't complicate thing, keep it simple
//Create a variable
var age = GetAgeFromSomewhere(id);
var profile = new {
Age = age,
IsLegal = age>18
}
What you want is not possible within object intializers. You cannot read properties of the object being initialized. (It does not matter, whether the type is anonymous or not.)
Instead, Create a class
public class Profile
{
public Profile(int id)
{
Age = GetAgeFromSomewhere(id);
}
public int Age { get; private set; }
public int IsLegal { get { return Age > 18; } }
}
Or getting the age the lazy way:
public class Profile
{
private readonly int _id;
public Profile(int id)
{
_id = id;
}
private int? _age;
public int Age {
get {
if (_age == null) {
_age = GetAgeFromSomewhere(_id);
}
return _age.Value;
}
}
public int IsLegal { get { return Age > 18; } }
}
or using the Lazy<T> class (starting with Framework 4.0):
public class Profile
{
public Profile(int id)
{
// C# captures the `id` in a closure.
_lazyAge = new Lazy<int>(
() => GetAgeFromSomewhere(id)
);
}
private Lazy<int> _lazyAge;
public int Age { get { return _lazyAge.Value; } }
public int IsLegal { get { return Age > 18; } }
}
Call it like this
var profile = new Profile(id);
If you don't want to have unnecessary variable, I suggest you use the current object instead :
var profile = new
{
Age = GetAgeFromSomewhere(id),
};
profile.IsLegal = profile.Age > 18;

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