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";
}
Related
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);
}
}
}
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
}
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 8 years ago.
Is something like the following possible or do you have to return the list and assign it afterwards? I get object reference not set to instance of an object.
public class MyCollection
{
public List<SomeObject> Collection { get; set; }
public List<SomeObject> CreateCollection()
{
// Is there a way to set the Collection property from here???
this.Collection.Add(new SomeObject()
{
// properties
});
}
}
...
MyCollection collection = new MyCollection();
collection.CreateCollection();
Yes, you can use an object initializer:
public List<SomeObject> CreateCollection()
{
// You may want to initialize this.Collection somehere, ie: here
this.Collection = new List<SomeObject>();
this.Collection.Add(new SomeObject
{
// This allows you to initialize the properties
Collection = this.Collection
});
return this.Collection;
}
Note that this will still potentially have an issue - you are never initializing this.Collection in any code you're displaying. You will need to initialize it to a proper collection in your constructor or via some other mechanism.
It is also an odd choice to have a "Create" method that initializes the local variable and returns a List<T>. Typically, you'd do one or the other. A more common approach would be to place this code within the constructor:
public class MyCollection
{
public IList<SomeObject> Collection { get; private set; } // The setter would typically be private, and can be IList<T>!
public MyCollection()
{
this.Collection = new List<SomeObject>();
this.Collection.Add(new SomeObject
{
Collection = this.Collection
});
}
}
You could then use it via:
MyCollection collection = new MyCollection();
var object = collection.Collection.First(); // Get the first element
That being said, in general, there is no real reason to make a custom class for a collection like this in most cases. Just using a List<SomeObject> directly is typically sufficient.
It's completely possible - you just have to instantiate it first, before you can use it:
public List<SomeObject> CreateCollection()
{
this.Collection = new List<SomeObject>(); // this creates a new list - the default if you just define a list but don't create it is for it to remain null
this.Collection.Add(new SomeObject()
{
// whatever
});
}
Of course, as pointed out in a comment, if you want that function to return a list, it would have to actually return the list. Presumably you mean public void CreateCollection(), though, since that was your question, whether you actually had to return a list (answer: no).
You must initialize this.Collection before adding elements into it.
public List<SomeObject> CreateCollection()
{
this.Collection = new List<SomeObject>();
this.Collection.Add(new SomeObject()
{
// properties
});
}
You can use a list initializer in this case:
public class Person
{
public string Name { get; set; }
public string Firstname { get; set; }
}
class Program
{
public static List<Person> Collection { get; set; }
public static List<Person> CreateCollection()
{
return new List<Person>()
{
new Person() { Name = "Demo", Firstname = "Demo1"},
new Person() { Name = "Demo", Firstname = "Demo1"},
};
}
static void Main(string[] args)
{
Collection = CreateCollection();
}
}
I have a List. This collection holds an object containing the properties of a class.
I want a distinct value of list with respect to any specific property of a class. I have attached some sample code; please check & let me know if you guys have any solutions:
class Test
{
public string firstname{get;set;}
public string lastname{get;set;}
}
class Usetheaboveclass
{
Test objTest=new Test();
List<Test> lstTest=new List<Test>();
objTest.firstname="test";
objTest.lastname="testing";
//Now i want a distinct value with respect to lastname.if i use
lstTest=lstTest.Distinct().Tolist();
//It will process according to all properties.
}
Can you suggest me a way to do this?
Try this approach.
var distinct = lstTest.GroupBy(item => item.lastname).Select(item => item.First()).ToList();
If you only need to do this for one property, override the Equals and GetHashCode methods in Test. These are what Distinct() uses to define duplicates.
If you need to do this for multiple properties, define an IEqualityComparer (the usage is documented in this MSDN article).
Or , you can implement a custom comparer
public class LastNameComparer : IEqualityComparer<Test>
{
public bool Equals(Test x, Test y)
{
if (x == null)
return y == null;
return x.lastname == y.lastname;
}
public int GetHashCode(Test obj)
{
if (obj == null)
return 0;
return obj.lastname.GetHashCode();
}
}
Then , use it like
lstTest = lstTest.Distinct(new LastNameComparer()).ToList();
You can use overloaded version of Distinct. Please see sample code below:
internal class Test
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
internal class LastNameComparer : IEqualityComparer<Test>
{
bool IEqualityComparer<Test>.Equals(Test x, Test y)
{
if (x.LastName == y.LastName)
return true;
return false;
}
int IEqualityComparer<Test>.GetHashCode(Test obj)
{
return 0; // hashcode...
}
}
private static void Main(string[] args)
{
Test objTest = new Test {FirstName = "Perry", LastName = "Joe"};
Test objTest1 = new Test {FirstName = "Prince", LastName = "Joe"};
Test objTest2 = new Test { FirstName = "Prince", LastName = "Jim" };
List<Test> lstTest = new List<Test> {objTest, objTest1, objTest2};
var distinct = lstTest.Distinct(new LastNameComparer()).ToList();
foreach (var test in distinct)
{
Console.WriteLine(test.LastName);
}
Console.Read();
}
Output of this will be:
Joe
Jim
It was suggested in this question, that I could cast a generic collection upward to a collection of objects with .Cast<object>. After reading up a bit on .Cast<>, I still can't get it a generic collection to cast into another generic collection. Why doesn't the following work?
using System.Collections.Generic;
using System.Linq;
using System;
namespace TestCast2343
{
class Program
{
static void Main(string[] args)
{
List<string> strings = new List<string> { "one", "two", "three" };
//gives error: cannot convert from 'System.Collections.Generic.List<string>'
//to 'System.Collections.Generic.List<object>'
//IEnumerable<string> items = strings.Cast<object>();
//this works
strings.Cast<object>();
//but they are still strings:
foreach (var item in strings)
{
System.Console.WriteLine(item.GetType().Name);
}
//gives error: cannot convert from 'System.Collections.Generic.List<string>'
//to 'System.Collections.Generic.List<object>'
ProcessCollectionDynamicallyWithReflection(strings);
Console.ReadLine();
}
static void ProcessCollectionDynamicallyWithReflection(List<object> items)
{
//...
}
}
}
Answer:
Thank you Reed, here's the code I got to work:
using System.Collections.Generic;
using System.Linq;
using System;
namespace TestCast2343
{
class Program
{
static void Main(string[] args)
{
List<string> strings = new List<string> { "one", "two", "three" };
List<int> ints = new List<int> { 34, 35, 36 };
List<Customer> customers = Customer.GetCustomers();
ProcessCollectionDynamicallyWithReflection(strings.Cast<object>().ToList());
ProcessCollectionDynamicallyWithReflection(ints.Cast<object>().ToList());
ProcessCollectionDynamicallyWithReflection(customers.Cast<object>().ToList());
Console.ReadLine();
}
static void ProcessCollectionDynamicallyWithReflection(List<object> items)
{
foreach (var item in items)
{
Console.WriteLine(item.GetType().Name);
}
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string Location { get; set; }
public string ZipCode { get; set; }
public static List<Customer> GetCustomers()
{
List<Customer> customers = new List<Customer>();
customers.Add(new Customer { FirstName = "Jim", LastName = "Jones", ZipCode = "23434" });
customers.Add(new Customer { FirstName = "Joe", LastName = "Adams", ZipCode = "12312" });
customers.Add(new Customer { FirstName = "Jake", LastName = "Johnson", ZipCode = "23111" });
customers.Add(new Customer { FirstName = "Angie", LastName = "Reckar", ZipCode = "54343" });
customers.Add(new Customer { FirstName = "Jean", LastName = "Anderson", ZipCode = "16623" });
return customers;
}
}
}
You're misusing Cast<T>.
First, here:
IEnumerable<string> items = strings.Cast<object>();
When you call strings.Cast<object>(), this will return IEnumerable<object>, not IEnumerable<string>. However, the items in the collection are still strings, but being held in references to objects.
Later, when you want to pass this into a method that takes a List<object>, you need to turn your IEnumerable<T> into an IList<T>. This could easily be done like so:
// Cast to IEnumerabe<object> then convert to List<object>
ProcessCollectionDynamicallyWithReflection(strings.Cast<object>().ToList());
It's because the Cast<> method does not return a List<T> type but instead an IEnumerable<T>. Add a .ToList call to the end and it will fix the problem.
strings.Cast<object>().ToList();
You can also address the casting problem from another perspective: fixing ProcessCollectionDynamicallyWithReflection since it's unnecessarily restrictive:
private static void ShowItemTypes(IEnumerable items)
{
foreach (object item in items)
{
string itemTypeName = (item != null) ? item.GetType().Name : "null";
Console.WriteLine(itemTypeName);
}
}
This should work:
IEnumerable<object> items = strings.Cast<object>();
Also, you can't pass an IEnumerable as a List into the ProcessCollectionDynamicallyWithReflection without a cast. So, this is even better:
List<object> items = strings.Cast<object>().ToList();
It was suggested in this question,
that I could cast a generic collection
upward to a collection of objects with
.Cast<object>
No, that is not correct. The Cast method doesn't cast the collection, it casts each item in the collection, returning a new collection containing the cast values.
You are creating the collection of object, but then you just throw it away as you are not assigning it to a variable. (Well, you are actually throwing away an expression that is capable of creating the collection, but that is not so relevant.)
You need to assign the collection to a variable to keep it, and as you need a List<object> you need to put the cast items in a list:
List<object> objects = strings.Cast<object>.ToList();
Perhaps you should consider using generics instead of casting to object:
static void ProcessCollectionDynamicallyWithReflection<T>(List<T> items)
That way you can write strongly typed code in the method, and you don't have to create a new collection just to be able to send it to the method.