Hello stackoverflow community!
Let's start!
I have one small class Person:
class Person
{
public string name { get; set; }
}
Аnd it has a descendant Employee:
class Employee : Person
{
public int salary { get; set; }
}
And second descendant is Guest:
class Guest: Person
{
public int id { get; set; }
}
Ok! looks good :)
Now I want to display a list of all employees OR guests in a single control ListView
I made a class (it really necessary) for list management PeopleList:
class PeopleList
{
public List<Person> People { get; set; }
...
public void LoadListFromFile()
{
// Load logic
this.People = new List<?>(source);
}
}
Do you see this question mark? No? Look at the code again!
How to create an instance of List that I can use my class something like this:
// This instance with the list of Person objects
PeopleList list = new PeopleList();
foreach (Employee e in list.People)
{
Debug.WriteLine(e.salary.toString());
}
// This instance with the list of Guest objects
PeopleList list = new PeopleList();
foreach (Guest g in list.People)
{
Debug.WriteLine(g.id.toString());
}
P.S. I'm new in c# and I think that I have a problem in architecture. And maybe you point me to the pattern solves my problem. I really need your help! Thank you!
I think you're after OfType, in the System.Linq library:
foreach (Employee e in personList.OfType<Employee>())
{
Debug.WriteLine(e.salary.toString());
}
foreach (Guest g in personList.OfType<Guest>())
{
Debug.WriteLine(g.id.toString());
}
The only field in Person that is shared with all of the decedents is the field 'name', therefore, if you are casting each item in your list to Person you will only have access to 'name'.
You have a few options to solve this issue. 1) you could move all of the common fields into the base class, but this is probably not want since it defeats the point of an object hierarchy, or 2) you can type check, using the "Is" keyword, each person in the list so see what type of person it is and then cast that Person to the appropriate decedent class before you operate on it.
For example:
foreach (Person p in list.People)
{
if(p is Employee)
{
Debug.WriteLine(((Employee)p).salary.toString());
}
if(p is Guest)
{
Debug.WriteLine(((Guest)p).Id.toString());
}
}
Here is an alternate more clear way of casting
foreach (Person p in list.People)
{
if(p is Employee)
{
Employee employee = p as Employee;
Debug.WriteLine(employee.salary.toString());
}
if(p is Guest)
{
Guest guest = p as Guest;
Debug.WriteLine(guest.Id.toString());
}
}
Additionally you can learn more about type checking using the "is" keyword here.
Also just for clarity, since it might not be obvious, some others have suggested that you use OfType in linq but this more of a way to filter like object from a list of mixed objects as opposed to actually type checking each one.
Enjoy!
As the comments are saying, you can do the following;
public void LoadListFromFile()
{
// Load logic
this.People = new List<Person>(source);
}
And then, cast where appropriate;
foreach (Person e in list.People.Where(p => p.GetType() == typeof(Employee)))
{
Debug.WriteLine(((Employee)e).salary.toString());
}
I think in your particular example, the inheritance structure is not adding much benefit, but I digress.
In your example, the easiest solution would be to override the ToString method in each one of your classes.
Having to know the exact type of the object before hand before you run some sort of calculation or display puts the onus of that calculation onto the calling code, if you make a 3rd type, you would have to update all possible references, giving you a maintenance nightmare.
The feel the logic of how to represent itself should be the responsibility (in your example) of the object itself.
This is what overriding ToString gives you, the responsibility of representing itself is then given to the thing that knows best (the object you're asking).
Related
I have the following code where i am adding some class objects in an Array.
Object[] ArrayOfObjects = new Object[] {typeof(Person), typeof(Company)};
Now if i want to iterate through my class items, how can i convert each item back to its original Type (such as Person and Company)? This might be possible using Reflection but i want to find out if C# has some built in functionality to achieve this.
foreach (var item in ArrayOfObjects)
{
// TODO Convert item back to Original Type (Person or Company)
// I am doing something like this but not working
var person = Convert.ChangeType(item, typeof(Person));
//I can not do this too as hardcoding the type inside the loop makes no sense
var person = item as Person; //I need to convert item as Person or Company so that i can automate the tasks here.
}
Much Thanks in advance.
Making some assumptions about your use case, you might benefit from using an interface to eliminate the need to convert the objects at all
Say you need to do the shared method Foo which belongs to both Company and Person
public interface ObjectWithFoo{
void Foo();
}
public class Person : ObjectWithFoo{
...
}
public class Company: ObjectWithFoo{
...
}
Then in your main code you create a list of ObjectWithFoo
ObjectWithFoo[] myArray = new ObjectWithFoo[]{new Person(), new Company()}
And then in your loop
foreach(var objectWithFoo in myArray)
objectWithFoo.Foo();
This way you don't need to cast at all, you can just use use the interface for everything. The added benefit is that it becomes more clear what your array is meant to be used for to yourself and others - it is used for only methods/attributes belonging to your interface. If you use an array of objects people can easily add an unsupported type or start using the list elsewhere and make your code a bit chaotic.
using System;
namespace PatternMatching
{
class Person
{
public void PersonMethod() => throw new Exception();
}
class Company
{
public void CompanyMethod() => throw new Exception();
}
class Program
{
static void Main(string[] args)
{
Object[] ArrayOfObjects = { new Person(), new Company() };
foreach (var item in ArrayOfObjects)
{
if (item is Person person)
{
person.PersonMethod();
}
if (item is Company company)
{
company.CompanyMethod();
}
}
}
}
}
Use pattern matching (C# 8.0+)
https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/may/csharp-8-0-pattern-matching-in-csharp-8-0
You can use even switch pattern for that.
I need to serialize/deserialize a KeyedCollection with protobuf-net, can I just serialize a list?
If so, what is the most efficient way to convert-back the List to the KeyedCollection?
Here follows a sample code that shows the case:
public class FamilySurrogate
{
public List<Person> PersonList { get; set; }
public FamilySurrogate(List<Person> personList)
{
PersonList = personList;
}
public static implicit operator Family(FamilySurrogate surrogate)
{
if (surrogate == null) return null;
var people = new PersonKeyedCollection();
foreach (var person in surrogate.PersonList) // Is there a most efficient way?
people.Add(person);
return new Family(people);
}
public static implicit operator FamilySurrogate(Family source)
{
return source == null ? null : new FamilySurrogate(source.People.ToList());
}
}
public class Person
{
public Person(string name, string surname)
{
Name = name;
Surname = surname;
}
public string Name { get; set; }
public string Surname { get; set; }
public string Fullname { get { return $"{Name} {Surname}"; } }
}
public class PersonKeyedCollection : System.Collections.ObjectModel.KeyedCollection<string, Person>
{
protected override string GetKeyForItem(Person item) { return item.Fullname; }
}
public class Family
{
public Family(PersonKeyedCollection people)
{
People = people;
}
public PersonKeyedCollection People { get; set; }
}
Solution?
.NET Platform Extensions 6 has an implementation of the KeyedCollection, KeyedByTypeCollection Class. This has a constructor which accepts an IEnumerable. The downside to this implementation is that the keys are the items, and it doesn't appear to allow you to change that. If you're already inheriting KeyedCollection, you may as well follow the implementation here and go by Microsoft's lead; they just iterate and call Add().
See also
Uses of KeyedByTypeCollection in .Net?
Can't seem to resolve KeyedByTypeCollection?
What are .NET Platform Extensions on learn.microsoft.com?
Linq with custom base collection
Collection Initializers
Previous thoughts
I'm also trying to tackle this issue from a Linq query perspective, possibly related posts:
Cannot implicitly convert type System.Collections.Generic.List back to Object after linq query
dotnet/runtime: Why is KeyedCollection abstract?
The core issue seems to be that KeyedCollectedion does not contain a constructor that takes any form of ICollection to initialize its data with. The base class of KeyedCollection, Collection, does however. The only option seems to be writing your own constructor for your KeyedCollection class that iterates over a collection and adds each element to the current instance.
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class VariableList<T> : KeyedCollection<string, T>
{
// KeyedCollection does not seem to support explicitly casting from an IEnumerable,
// so we're creating a constructor who's sole purpose is to build a new KeyedCollection.
public VariableList(IEnumerable<T> items)
{
foreach (T item in items)
Add(item);
}
// insert other code here
}
This seems really inefficient though, so I hope someone corrects me...
Edit: John Franco wrote a blogpost wherein they hack together a solution for genericly casting a List with covariants (in 2009!) This doesn't look like a very good way to do things.
Looking at System.Linq.Enumerable's implementation of ToList, Linq also iterates and Adds to the new collection.
Suppose I have a List of Person (which is a class). It contains about 20 field (Name, Surname, Age, DateOfBirthdate, and so on). So I got this list:
var listOfPersons= MyContext.Persons.Cast<Person>();
Now, I need to iterate through this List, and for each Person adding a new field (which it is not present in the class), called, let's say, CurrentDateTime.
I could create a new object, with the new field, and "copy & paste" values from Person to the new Class. Somethings like:
PersonNew newPerson = new PersonNew("Name", "Surname", "Age", "DateOfBirthdate", ... "CurrentDateTime");
But this is very bad if in the future I change the Person class. So, is there a strategy to "extending Person" with a new field? That takes the Person instance (whatever it is) and adds the new field?
You can create some static method that create PersonNew from Person using Automapper.
public class PersonNew : Person
{
public static PersonNew CreateFromPerson(Person person, DateTime currentDateTime)
{
var newPerson = Mapper.Map<PersonNew>(person);
newPerson.CurrentDateTime = currentDateTime;
}
}
I think that the solution you described works fine. If you want to keep track of each person's birthday without extending the Person class, you might use a Dictionary object
var listOfPersons = MyContext.Perons.Cast<Person>();
Dictionary<Person, DateTime> birthdays = new Dictionary<Person, DateTime>
foreach(Person person in listOfPersons)
{
birthdays.Add(person, getBirthday(person);
}
One solution is to make your class partial, and add your field in another partial definition of your class:
public partial class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
...
}
...
public partial class Person
{
public DateTime CurrentDateTime { get; set; }
}
...
var listOfPersons = MyContext.Persons.Cast<Person>();
foreach (var person in listOfPersons)
{
person.CurrentDateTime = ....
}
Do note that you will use the same instance of your class.
First I would suggest using extension methods for projecting collections instead of iterating. Like that:
var newCollection = oldCollection.Select(entity => MakeNewType(entity))
Second, it's not completely clear what you mean by "extending Person" with a new field. Here are the couple of ways you can accomplish that.
1) Make another class with the new field and map it to the old one. This is a common scenario for asp.net mvc application where you map models to the appropriate viewmodels. Automapper is useful for these types of scenario (see Sławomir Rosiek anwser)
2) Take advantage of dlr in c# 4+. Yuo will lose the intellisense for dynamic objects, but they canned be passed around functions
var newPeople = people.Select(p =>
{
dynamic expando = new ExpandoObject();
expando.Id = p.Id;
expando.FirtName = p.FirtName;
/* ... */
expando.CurrentDateTime = DateTime.Now;
return expando;
});
3) Use Anonymous types. Anonymous types cannot be passed to another functions, so this approach is useful when you need to quickly project data inside a single method and calculate some result
var newPeople = people.Select(p => new
{
Id = p.Id,
FirtName = p.FirtName,
/* ... */
CurrentDateTime = DateTime.Now
});
in both cases you can now access newly "created" property:
foreach(var p in newPeople)
{
Console.WriteLine("CurrentDateTime: {0}", p.CurrentDateTime);
}
4) If you really need to create a fully featured .net class at runtime you can use Reflection.Emit. This scenario is typically used to create dynamic proxies - subclasses which implement some functionality only known at runtime. Entity framework does this.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What’s the return type of an anonymous class
I'm creating an anonymous type with query like the following:
Caller code:
var query= from p in _db.ExecuteDataSet(SQL).Tables[0].AsEnumerable()
select new {
ProductCode = p.Field<string>("PRODUCT_CODE"),
ProductName = p.Field<string>("PRODUCT_NAME")
};
foreach(var product in query)
{
WriteProduct(product);
}
Method is like:
void WriteProduct(object prod)
{
// access the product
}
I fail to get the correct Parameter Type for the WriteProduct method. Please help me.
Yes you can.
public class Program
{
private static void Thing(dynamic other)
{
Console.WriteLine(other.TheThing);
}
private static void Main()
{
var things = new { TheThing = "Worked!" };
Thing(things);
}
}
But as a small, minor detail, DON'T!
Anonymous types are anonymous for a reason, they aren't first class entities in your code, they're more of a convenience. If a type is that important, define it as such.
There are 3 ways to talk to an anonymous type:
reflection (obtain the properties via obj.GetType().GetProperties() / prop.GetValue(obj, null), etc)
dynamic (i.e. obj.ProductCode and obj.ProductType, for dynamic obj) - an optimized and prettier version of the above
cast-by-example : DO NOT USE
Your WriteProduct must use one of those; or alternatively : use something other than an anonymous type; a Tuple<...>, maybe (although that tends to make it hard to know what the data is) - or an appropriately defined custom interface, class or struct.
Correct me if I'm wrong, but I think you should create a temporary class to store the product.
select new TempProduct {
productCode = p.Field<string>("PRODUCT_CODE"),
productName = p.Field<string>("PRODUCT_NAME")
};
With e.g a class like this
public class TempProduct
{
public String productCode { get; set; }
public String productName { get; set; }
}
This isn't exactly what you are asking for, but your select has only two properties, so how about passing these two to the method?
foreach(var product in query)
{
WriteProduct(product.ProductCode, product.ProductName);
}
// ...
void WriteProduct(string productCode, string productName)
{
// ...
}
I have a
ObservableCollection<BasicClass> allCollection;
ObservableCollection<BasicClass> selectedCollection;
where
BasicClass
{
public Name {get;set;}
public Age {get;set;}
}
Now I added many BasicClass items to allCollection and only selected BasicClass to selectedCollection
SomeWhere I want to add items in selectedCollection which are not there in allCollection.
I tried this
foreach(var a in allCollection)
{
foreach(var s in selectedCollection)
if(a.Name!=s.Name)
//selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
But the problem is that this code is adding new BasicClass for each and every unmatched name,
but my actuall requirement is, for each Name of allCollection compare all selectedCollection items. If it is not there then add else move for next Item.
LINQ solution could help this? Actually I achieved this by more if and flags but That looks ver hectic.
My traditional solution
foreach(var a in allCollection)
{
bool same = false;
foreach(var s in selectedCollection)
if(a.Name==s.Name)
same=true;
}
if(same==false)
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
And I hate this..
EDIT:
I don't want compare collection to collection.
I want to compare collection1 value to collection2 all values, and if it not there then I want to add
Are you sure you don't just need this?
foreach(var a in allCollection)
{
if (!selectedCollection.Contains(a))
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
EDIT
I've just seen your comment below about matching on name only, so the above is not really what you want:). Try this approach instead:
foreach(var a in allCollection)
{
if (!selectedCollection.Any(s => a.Name == s.Name))
{
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
}
EDIT
As Chris suggested you could also use "Except" to create a collection. I'm not sure this gains much, it may be quicker but it involves writing the comparer code and creates a new temporary collection. However, it is pretty succinct E.g. Once you had the comparaer written you would just need this to add your missing items to the collection:
selectedCollection.Concat(allCollection.Except(selectedCollection));
So basically you need a 'where-not-in'? Linq->Except is the way to go, to filter on BasicClass.name only implement the IEqualityComparer for Except.
I'm not sure I understood your requirements correctly, so i may be missing the point...
Your BasicClass should implement the IEquatable<BasicClass> interface, so that two instances of BasicClass can be compared for equality:
class BasicClass : IEquatable<BasicClass>
{
public Name {get;set;}
public Age {get;set;}
public bool Equals(BasicClass other)
{
if (other == null)
return false;
return string.Equals(this.Name, other.Name);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
Now you can use the Except method to find items that are in allCollection but not in selectedCollection:
BasicClass[] notSelected = allCollection.Except(selectedCollection).ToArray();
foreach(BasicClass item in notSelected)
{
selectedCollection.Add(item);
}
Alternatively, you can implement a IEqualityComparer<BasicClass> and pass it to Except (instead of implementing IEquatable<BasicClass> in BasicClass)
You're right, this is more easily accomplished with Linq:
var itemsToAdd = allCollection.Except(selectedCollection);
foreach (var item in itemsToAdd)
selectedCollection.Add(item);
On the other hand, this is just going to make both lists contain the exact same items. Sure this is what you want?
If BasicItem overrides 'Equals' and 'GetHashCode' based off of Name, then this is all you need. If it doesn't, then you will also need to implement an IEqualityComparer:
//Allows us to compare BasicItems as if Name is the key
class NameComparer: IEqualityComparer<BasicItem>
{
public bool Equals(BasicItem first, BasicItem second)
{
return first.Name == second.Name;
}
public int GetHashCode(BasicItem value)
{
return value.Name.GetHashCode;
}
}
You now pass an instance of this class to Except:
var itemsToAdd = allCollections.Except(selectedCollection, new NameComparer());