Use LINQ to retrieve multiple scalar values - c#

I am wondering if there is some clever way to retrieve data from an enumerable using LINQ when individual values from multiple records are needed.
For example, let's say you have a person with three different phone fields:
public class Person
{
public Phone HomePhone { get; set; }
public Phone WorkPhone { get; set; }
public Phone CellPhone { get; set; }
}
...but the phone list is stored in a normalized format:
public enum PhoneType
{
Home, Work, Cell
}
public class Phone
{
public PhoneType Type { get; set; }
public string Number { get; set; }
}
static public IEnumerable<Phone> GetPhoneList()
{
yield return new Phone { Type = PhoneType.Home, Number = "8005551212" };
yield return new Phone { Type = PhoneType.Work, Number = "8005551313" };
yield return new Phone { Type = PhoneType.Cell, Number = "8005551414" };
}
If you needed to populate Person, you could write a loop, and get everything you need in one pass:
public static Person GetPerson1()
{
var result = new Person();
foreach (var ph in GetPhoneList())
{
switch (ph.Type)
{
case PhoneType.Home: result.HomePhone = ph; break;
case PhoneType.Work: result.WorkPhone = ph; break;
case PhoneType.Cell: result.CellPhone = ph; break;
}
}
return result;
}
But if you wanted to use LINQ, it seems like three passes may be needed:
public static Person GetPerson2()
{
return new Person
{
HomePhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Home ),
WorkPhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Work ),
CellPhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Cell )
};
}
Is there a clever way to use LINQ to get it all with only one pass over the enumeration?
Here is a link to a Fiddle if you'd like to explore my code.
(I am aware I could use a dictionary or other data structure to solve this particular problem; this is just an example.)

Normally, you can't do this in LINQ.
If you really want to, you can create a Foreach extension method and do the same as your GetPerson1 method.
public static class Ext
{
public static void Foreach<T>(this IEnumerable<T> e, Action<T> action)
{
foreach (T item in e)
{
action(item);
}
}
}
and then
public static Person GetPerson2()
{
var p = new Person();
var pl = GetPhoneList();
pl.Foreach(ph =>
{
switch (ph.Type)
{
case PhoneType.Home: p.HomePhone = ph; break;
case PhoneType.Work: p.WorkPhone = ph; break;
case PhoneType.Cell: p.CellPhone = ph; break;
}
});
return p;
}
But you really shouldn't. LINQ is meant to operate on IEnumerables (item by item), and LINQ functions should be without side effects, while your foreach loop and Foreach extension methods are only creating side effects, changing the state of the Person object.
And, besides, the fact that you need a 'clever way' should be an indication that this is not the way it's meant to be used :)
There's a great article by Eric Lippert with more details here: https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

If there is no guarantee that numbers from the same person come in a sequence then you have to enumerate the list until you find all the numbers. It does not seem to me this is a good candidate for LINQ, whose purpose is to make the code more readable. Your foreach is just fine, and I would just break the loop when all numbers are found.
If you want to enumerate all the persons, and not just one then Dictionary approach is probably most effective. GroupBy internally uses a dictionary and you can use GroupBy to collect all the numbers belonging to a person, and then Aggregate to make a Person out of them. Let's assume there is some property Phone.PersonID, and also Person.PersonID, then you would have something like this:
GetPhoneList()
.GroupBy(x => x.PersonID)
.Select(x => x.Aggregate(new Person() { PersonID = x.Key },
(person, phone) =>
{
switch (phone.Type)
{
case PhoneType.Home: person.HomePhone = phone; break;
case PhoneType.Work: person.WorkPhone = phone; break;
case PhoneType.Cell: person.CellPhone = phone; break;
}
return person;
}));
I assume here that GetPhoneList() returns all the phones of all persons.

Related

Cosmos DB - Use ARRAY_CONTAINS in LINQ

I have collection of documents in Cosmos DB. Document can have inner array of objects. So model look like this:
public class Document
{
public string Id { get; set; }
public IList<InnerDocument> InnerDocuments { get; set; }
}
public class InnerDocument
{
public string Type { get; set; }
public string Created { get; set; }
}
I need to get all inner documents if at least one of them has certain type.
If I create query like this:
var innerDocument = new InnerDocument()
{
Type = "foo"
};
context.CreateDocumentQuery<Document>(uri, feedOptions)
.Where(d => d.id == "sample" && d.InnerDocuments.Contains(innerDocument));
it translate like this:
SELECT * FROM root
WHERE (root[\"id\"] = "sample"
AND ARRAY_CONTAINS(root[\"innerDocuments\"], {\"type\":\"foo\"}))
but it returns nothing, because no inner document look like this (all inner documents has also Created) so I need to add third parameter to ARRAY_CONTAINS (which tell that only part match on document is enough) so it should look like this:
SELECT * FROM root
WHERE (root[\"id\"] = "sample"
AND ARRAY_CONTAINS(root[\"innerDocuments\"], {\"type\":\"foo\"}, true))
My problem is that I did not figure out how to pass third parameter in linq. I also tried write IEqualityComparer, which always return true but with no effect (well efect was that I got exception..).
Do you have any idea how could I pass that param in linq?
Thanks.
as far as I know, unfortunately there is no LINQ equivalent for the ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr) overload. To achieve your scenarios, for now you can use SQL query. We are currently working on a set of changes that will enable LINQ for this scenario.
Edit: the available alternative is to use the Any operator with the filters on the property you want to match. For example, the SQL filter: ARRAY_CONTAINS(root.addresses, {"city": "Redmond"}, TRUE) is equivalent to this LINQ expression: addresses.Any(address => address.city == "Redmond")
If I understand correctly, you wish to retrieve all documents that have any inner document in the array with a given property value ("foo" in this example).
Normally, you would use .Where(d => d.InnerDocuments.Any(i => i.Type == "foo")), but Any is not supported yet by the Cosmos LINQ provider.
Instead, you can use this construct as a work-around:
context.CreateDocumentQuery<Document>(uri, feedOptions)
.Where(d => d.Id == "sample")
.SelectMany(d => d.InnerDocuments.Where(i => i.Type == "foo").Select(i => d));
According to this thread Microsoft has recently started working on a real Any feature for the Cosmos LINQ provider.
My solution was slightly more of a hack than a solution, but it works temporarily until the full functionality for .Any() exists.
I use Expressions to dynamically build the Where predicate for my documents, allowing me pass in a CosmosSearchCriteria object which has a list of CosmosCriteria objects as below:
public class CosmosCriteria
{
public CosmosCriteria()
{
ContainsValues = new List<string>();
}
public CosmosCriteriaType CriteriaType { get; set; }
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
public ConvertedRuleComparitor Comparitor { get; set; }
public DateRange Dates { get; set; }
public List<string> ContainsValues { get; set; }
}
This allows me to query any property of the Contact model by essentially passing in the PropertyName and PropertyValue.
I haven't looked into the other workaround in here to see if I can make it work with my expression tree building, at the minute I can't afford the time to investigate.
public async Task<CosmosSearchResponse<Model.Contact>>
GetContactsBySearchCriteriaAsync(int pageSize, long companyId,
CosmosSearchCriteria searchCriteria, string continuationToken = null)
{
var collectionName = CreateCollectionName(companyId, Constants.CollectionType.Contacts);
var feedOptions = new FeedOptions { MaxItemCount = pageSize };
if (!String.IsNullOrEmpty(continuationToken))
{
feedOptions.RequestContinuation = continuationToken;
}
var collection = UriFactory.CreateDocumentCollectionUri(
Configuration.GetValue<string>(Constants.Settings.COSMOS_DATABASE_SETTING),
collectionName);
IOrderedQueryable<Model.Contact> documents = Client.CreateDocumentQuery<Model.Contact>(
collection,
feedOptions
);
documents = (IOrderedQueryable<Model.Contact>)documents.Where(document => document.deleted != true);
bool requiresConcatenation = false;
foreach (var criteria in searchCriteria.Criteria)
{
switch (criteria.CriteriaType)
{
case Constants.CosmosCriteriaType.ContactProperty:
// This is where predicates for the documents.Where(xxxx)
// clauses are built dynamically with Expressions.
documents = AddContactPropertyClauses(documents, criteria);
break;
case Constants.CosmosCriteriaType.PushCampaignHistory:
requiresConcatenation = true;
break;
}
}
documents = (IOrderedQueryable<Model.Contact>)documents.AsDocumentQuery();
/*
From this point onwards, we have to do some wizardry to get around the fact that there is no Linq to SQL
extension overload for the Cosmos DB function ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr).
The feature is planned for development but is not yet ready.
Keep an eye on the following for updates:
https://stackoverflow.com/questions/52412557/cosmos-db-use-array-contains-in-linq
https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/11503872-support-linq-any-or-where-for-child-object-collect
*/
if (requiresConcatenation)
{
var sqlString = documents.ToString();
var jsonDoc = JsonConvert.DeserializeObject<dynamic>(sqlString); // Have to do this to remove the escaping
var q = (string)jsonDoc.query;
var queryRootAlias = Util.GetAliasNameFromQuery(q);
if (queryRootAlias == string.Empty)
{
throw new FormatException("Unable to parse root alias from query.");
}
foreach (var criteria in searchCriteria.Criteria)
{
switch (criteria.CriteriaType)
{
case Constants.CosmosCriteriaType.PushCampaignHistory:
q += string.Format(" AND ARRAY_CONTAINS({0}[\"CampaignHistory\"], {{\"CampaignType\":1,\"CampaignId\":{1}, \"IsOpened\": true }}, true) ", queryRootAlias, criteria.PropertyValue);
break;
}
}
documents = (IOrderedQueryable<Model.Contact>)Client.CreateDocumentQuery<Model.Contact>(
collection,
q,
feedOptions
).AsDocumentQuery();
}
var returnValue = new CosmosSearchResponse<Model.Contact>();
returnValue.Results = new List<Model.Contact>();
Console.WriteLine(documents.ToString());
var resultsPage = await ((IDocumentQuery<Model.Contact>)documents).ExecuteNextAsync<Model.Contact>();
returnValue.Results.AddRange(resultsPage);
if (((IDocumentQuery<Model.Contact>)documents).HasMoreResults)
{
returnValue.ContinuationToken = resultsPage.ResponseContinuation;
}
return returnValue;
}
Hope this helps, or if someone has a better way, please do tell!
Dave

Search value in List<T> inside another List<T>

i have class strucur something like this
List<MainCat> AllCat;
public class MainCat
{
public string id { get; set; }
public string name { get; set; }
public List<subcat> subcat { get; set; }
}
public class subcat
{
public string id { get; set; }
public string name { get; set; }
public List<subsubcat> subsubcat { get; set; }
}
public class subsubcat
{
public string id { get; set; }
public string name { get; set; }
}
i want to get name by id,
for example i know the id is 69
i want get output like this
MainCat.name > subcat.name > subsubcat.name (if 69 found in subsubcat)
MainCat.name > subcat.name (if 69 found in subcat)
MainCat.name (if 69 found in MainCat)
If I've understood your requirement properly, this is a case where the query syntax can work wonders:
IEnumerable<string> MyFunc(IEnumerable<MainCat> mainCategories, string idToMatch)
{
return (from main in mainCategories
where main.id == idToMatch
select main.name)
.Concat(from main in mainCategories
from sub in main.subcat
where sub.id == idToMatch
select string.Format("{0} > {1}", main.name, sub.name))
.Concat(from main in mainCategories
from sub in main.subcat
from subsub in sub.subsubcat
where subsub.id == idToMatch
select string.Format("{0} > {1} > {2}", main.name, sub.name, subsub.name));
}
If you're only interested in the first match, this can be called like
string resultName = MyFunc(AllCat, "69").FirstOrDefault();
Because the query uses deferred execution, this will avoid calling the more complex queries if a match is found in the main category.
It is also possible to use the SelectMany function with the function call syntax, however, it gets much harder to follow e.g. the following is how I re-wrote the contents of the second .Concat(...) call in order to illustrate:
mainCategories.SelectMany(main => main.subcat, (main, sub) => new { Main = main, Sub = sub })
.SelectMany(pair => pair.Sub.subsubcat, (pair, subsub) => new { Main = pair.Main, Sub = pair.Sub, SubSub = subsub})
.Where(triplet => triplet.SubSub.id == idToMatch)
.Select(triplet => string.Format("{0} > {1} > {2}", triplet.Main, triplet.Sub, triplet.SubSub));
As I understand it, the query syntax compiles to something very similar to this behind the scenes.
Update after answer accepted, and I came back to look at my code again:
Another possibility would be to add an interface to all 3 classes (or unify them into a single class or derive from a common base class depending on real use case).
This allows a recursive implementation that can search to arbitrary depth (below are 2 different Linq-based implementations depending on whether you have a preference for one or other syntax):
public interface ITreeCat
{
string id { get; }
string name { get; }
IEnumerable<ITreeCat> subcat { get; }
}
// add explicit interface implemetantion to existing 3 classes
// e.g.
// IEnumerable<ITreeCat> ITreeCat.subcat { get { return subsubcat; } }
// IEnumerable<ITreeCat> ITreeCat.subcat { get { return Enumerable.Empty<ITreeCat>(); } }
IEnumerable<string> MyFunc(IEnumerable<ITreeCat> categories, string idToMatch, string prefix = "")
{
return (from cat in categories
where cat.id == idToMatch
select prefix + cat.name)
.Concat(from cat in categories
from recursiveResult in MyFunc(cat.subcat, idToMatch, prefix + cat.name + " > ")
select recursiveResult);
}
IEnumerable<string> MyFunc2(IEnumerable<ITreeCat> categories, string idToMatch, string prefix = "")
{
return categories.Where(cat => cat.id == idToMatch)
.Select(cat => prefix + cat.name)
.Concat(categories.SelectMany(cat => MyFunc2(cat.subcat, idToMatch, prefix + cat.name + " > ")));
}
This has the advantage that it continues to work if you later add a subsubsubcat etc.
All of the above code examples use a breadth-first search, and repeatedly enumerate the "parent" categories each time they go one level deeper.
In some applications a depth-first search may be a better choice, as each list is only enumerated once, in which case it's much easier to use foreach rather than Linq. Again, a recursive version is more concise than 3 nested loops with different classes:
IEnumerable<string> MyFuncDepthFirst(IEnumerable<ITreeCat> categories, string idToMatch)
{
foreach(var cat in categories)
{
if (cat.id == idToMatch)
yield return cat.name;
foreach (var subResult in MyFuncDepthFirst(cat.subcat, idToMatch))
yield return string.Format("{0} > {1}", cat.name, subResult);
}
}
This still assumes that multiple matches can occur. If we're just after the first match, then there's no need to use an iterator block at all, and the above function can be modified to return a simple string:
string FirstMatchingIdDepthFirst(IEnumerable<ITreeCat> categories, string idToMatch)
{
foreach(var cat in categories)
{
if (cat.id == idToMatch)
return cat.name;
string subResult = FirstMatchingIdDepthFirst(cat.subcat, idToMatch);
if(subResult != null)
return string.Format("{0} > {1}", cat.name, subResult);
}
return null;
}
var list = this.AllCat.Where(t=>t.subcat.Any(s=> subsubcat.contains(s));
You can go for a method like below
public static Type Find(string id, MainCat m)
{
if (m.id.Equals(id))
{
return m.GetType();
}
if (m.subcat.Any(a => a.id.Equals(id)))
{
return typeof(subcat);
}
if (m.subcat.Any(a => a.subsubcat.Any(b => b.id.Equals(id))))
{
return typeof(subsubcat);
}
return null;
}
and perform the search. Find the gist, https://gist.github.com/IshamMohamed/33d75064789d77d88404b8ffc9a17e94
In this way you can increase the number of inner lists (eg: subsubsubcat)

How do I dynamically apply a conditional operator to a field using the official MongoDB-CSharp-Driver?

I'm trying to generate a query that finds all large, red things with a cost greater than 3.
This query seems to be what I'm after:
{ "color" : "red", "size" : "large", "cost" : { "$gt" : 3.0 } }
But, I am unable to find an elegant way to create the cost condition using the official MongoDB CSharp Driver. This is one hack which seems to create the query:
QueryConditionList gt = Query.GT("cost", BsonDouble.Create(3));
QueryDocument query = new QueryDocument();
query.Add("color", "red");
query.Add("size", "large");
query.Add(gt.ToBsonDocument().Elements);
List<BsonDocument> results = events.Find(query).ToList();
Another way to do it which seems to work is like this:
QueryDocument query = new QueryDocument();
query.Add("color", "red");
query.Add("size", "large");
query.Add("cost", new BsonDocument("$gt", BsonDouble.Create(3)));
List<BsonDocument> results = events.Find(query).ToList();
Are either of these approaches a good way to accomplish this? Is there another?
I need to use techniques which allow me to dynamically build the query and add fields that will be involved in the query. I was hoping to find a way to add a condition via query.Add( ) but I don't know if that is possible.
Any help is appreciated.
You can use the Query builder throughout, like so:
var query = Query.And(
Query.EQ("color", "red"),
Query.EQ("size", "large"),
Query.GT("cost", 3)
);
update Sorry, I see what you're asking, now.
You could do something like this, also:
int i = 0;
var qc = QueryComplete[3];
qc[i++] = Query.EQ("color", "red");
qc[i++] = Query.EQ("size", "large");
qc[i++] = Query.GT("cost", 3);
var query = Query.And(qc);
This way, you can still use the builder methods and have it be dynamic.
You can data drive it in a brute force manner, just build a tree of "QueryElement" and call BuildQuery to recursively build it as in this example class:
public class QueryElement
{
public enum eOperator
{
AND, OR, EQ, NE, GT, GTE, LT, LTE //etc.
};
public eOperator Operator { get; set; }
public string Field { get; set; }
public BsonValue Value { get; set; }
public List<QueryElement> Children { get; set; }
public IMongoQuery BuildQuery()
{
int i = 0;
var qc = new IMongoQuery[(Children!=null)?Children.Count:0];
if (Children != null)
{
foreach (var child in Children)
{
qc[i++] = child.BuildQuery();
}
}
switch (Operator)
{
// multiple element operators
case eOperator.AND:
return Query.And(qc);
case eOperator.OR:
return Query.And(qc);
// single element operators
case eOperator.EQ:
return Query.EQ(Field, Value);
case eOperator.NE:
return Query.NE(Field, Value);
case eOperator.GT:
return Query.GT(Field, Value);
case eOperator.GTE:
return Query.GTE(Field, Value);
case eOperator.LT:
return Query.LT(Field, Value);
case eOperator.LTE:
return Query.LTE(Field, Value);
}
return null;
}
}

Why return Interfaces eg IEnumerable, IList - example of refactoring

Question: Have I got this example backwards?
Is the reason to return Interface so that:
Try1:
public class Thing
{
public string name { get; set; }
public int age { get; set; }
public IList<Thing> giveMeAllThings()
{
IList<Thing> listOfThings = new List<Thing>();
Thing thing1 = new Thing { name = "phone", age = 3 };
Thing thing2 = new Thing { name = "waterbottle", age = 2 };
Thing thing3 = new Thing { name = "pinecone", age = 17 };
listOfThings.Add(thing1);
listOfThings.Add(thing2);
listOfThings.Add(thing3);
return listOfThings;
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void UnitTestThings()
{
Thing thing = new Thing();
IEnumerable<Thing> listOfThings = thing.giveMeAllThings();
Assert.AreEqual(3, listOfThings.Count()); // linq count
Assert.IsTrue(listOfThings.Any(t => t.name == "phone" && t.age == 3));
Assert.IsTrue(listOfThings.Any(t => t.name == "waterbottle" && t.age == 2));
Assert.IsTrue(listOfThings.Any(t => t.name == "pinecone" && t.age == 17));
}
}
try2. I figure out that as I only need IEnumerable then I can just return that.
public IEnumerable<Thing> giveMeAllThings()
{
IList<Thing> listOfThings = new List<Thing>();
Thing thing1 = new Thing { name = "phone", age = 3 };
Thing thing2 = new Thing { name = "waterbottle", age = 2 };
Thing thing3 = new Thing { name = "pinecone", age = 17 };
listOfThings.Add(thing1);
listOfThings.Add(thing2);
listOfThings.Add(thing3);
foreach (Thing t in listOfThings)
yield return t;
}
Edit:
So if I need a List in one method but not another, then the most 'General' interface possible to return is an IList:
public IList<Thing> giveMeAllThings()
{
IList<Thing> listOfThings = new List<Thing>();
Thing thing1 = new Thing { name = "phone", age = 3 };
Thing thing2 = new Thing { name = "waterbottle", age = 2 };
Thing thing3 = new Thing { name = "pinecone", age = 17 };
listOfThings.Add(thing1);
listOfThings.Add(thing2);
listOfThings.Add(thing3);
return listOfThings;
}
[TestMethod]
public void UnitTestThings()
{
Thing thing = new Thing();
IEnumerable<Thing> listOfThings = thing.giveMeAllThings();
Assert.AreEqual(3, listOfThings.Count()); // linq count
}
[TestMethod]
public void UnitTestThingsWhereINeedAnActualList()
{
Thing thing = new Thing();
IList<Thing> listOfThings = thing.giveMeAllThings();
Assert.AreEqual(3, listOfThings.Count); // List count
}
Is the reason to return Interface so that:
The main reason to do this is that, by returning the most "general" purpose interface possible, you have more flexibility in changing your internal implementation to something "better" (ie: more efficient, simpler code, whatever the criteria may be) later, without breaking your public API.
For example, if you switch from IList<T> to IEnumerable<T>, you could switch your code around to use iterators (yield return) instead of a collection. You may decide that a different collection (instead of List<T>) works better in your code, and switch to it. If that second collection didn't implement IList<T>, it would require you to change your public API.
Well, you could return ICollection<Thing> which has .Count, Thing[] which has .Length, or IEnumberable<Thing> which has the linq extension method .Count() . You don't necessarily need IList<Thing> if you just want the count of elements.
Using interfaces or basic return types also shows intent as to how the return type is intended to be used.
I tend to use IEnumerable<T> most of the time.
I consider using arrays (or IList<T>) when indexing is required.
An IEnumerable is less specific than an IList. I try to return the least specific thing that will still work.
Supposing I were returning an IDictionary, there's the tempation to return:
IEnumerable<ICollection<KeyValuePair<Key, Value>>
But I think that doesn't accurately describe what is happening, even though it is less specific than:
IDictionary<Key, Value>

How to compare two distinctly different objects with similar properties

This is all in C#, using .NET 2.0.
I have two lists of objects. They are not related objects, but they do have certain things in common that can be compared, such as a GUID-based unique identifier. These two lists need to be filtered by another list which just contains GUIDs which may or may not match up with the IDs contained in the first two lists.
I have thought about the idea of casting each object list to just object and sorting by that, but I'm not sure that I'll be able to access the ID property once it's cast, and I'm thinking that the method to sort the two lists should be somewhat dumb in knowing what the list to be sorted is.
What would be the best way to bring in each object list so that it can be sorted against the list with only the IDs?
You should make each of your different objects implement a common interface. Then create an IComparer<T> for that interface and use it in your sort.
Okay, if you have access to modify your original classes only to add the interface there, Matthew had it spot on. I went a little crazy here and defined out a full solution using 2.0 anonymous delegates. (I think I'm way addicted to 3.0 Lambda; otherwise, I probably would've written this out in foreach loops if I was using 2005 still).
Basically, create an interface with the common properties. Make yoru two classes implement the interface. Create a common list casted as the interface, cast and rip the values into the new list; remove any unmatched items.
//Program Output:
List1:
206aa77c-8259-428b-a4a0-0e005d8b016c
64f71cc9-596d-4cb8-9eb3-35da3b96f583
List2:
10382452-a7fe-4307-ae4c-41580dc69146
97f3f3f6-6e64-4109-9737-cb72280bc112
64f71cc9-596d-4cb8-9eb3-35da3b96f583
Matches:
64f71cc9-596d-4cb8-9eb3-35da3b96f583
Press any key to continue . . .
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
//test initialization
List<ClassTypeA> list1 = new List<ClassTypeA>();
List<ClassTypeB> list2 = new List<ClassTypeB>();
ClassTypeA citem = new ClassTypeA();
ClassTypeB citem2 = new ClassTypeB();
citem2.ID = citem.ID;
list1.Add(new ClassTypeA());
list1.Add(citem);
list2.Add(new ClassTypeB());
list2.Add(new ClassTypeB());
list2.Add(citem2);
//new common list.
List<ICommonTypeMakeUpYourOwnName> common_list =
new List<ICommonTypeMakeUpYourOwnName>();
//in english, give me everything in list 1
//and cast it to the interface
common_list.AddRange(
list1.ConvertAll<ICommonTypeMakeUpYourOwnName>(delegate(
ClassTypeA x) { return (ICommonTypeMakeUpYourOwnName)x; }));
//in english, give me all the items in the
//common list that don't exist in list2 and remove them.
common_list.RemoveAll(delegate(ICommonTypeMakeUpYourOwnName x)
{ return list2.Find(delegate(ClassTypeB y)
{return y.ID == x.ID;}) == null; });
//show list1
Console.WriteLine("List1:");
foreach (ClassTypeA item in list1)
{
Console.WriteLine(item.ID);
}
//show list2
Console.WriteLine("\nList2:");
foreach (ClassTypeB item in list2)
{
Console.WriteLine(item.ID);
}
//show the common items
Console.WriteLine("\nMatches:");
foreach (ICommonTypeMakeUpYourOwnName item in common_list)
{
Console.WriteLine(item.ID);
}
}
}
interface ICommonTypeMakeUpYourOwnName
{
Guid ID { get; set; }
}
class ClassTypeA : ICommonTypeMakeUpYourOwnName
{
Guid _ID;
public Guid ID {get { return _ID; } set { _ID = value;}}
int _Stuff1;
public int Stuff1 {get { return _Stuff1; } set { _Stuff1 = value;}}
string _Stuff2;
public string Stuff2 {get { return _Stuff2; } set { _Stuff2 = value;}}
public ClassTypeA()
{
this.ID = Guid.NewGuid();
}
}
class ClassTypeB : ICommonTypeMakeUpYourOwnName
{
Guid _ID;
public Guid ID {get { return _ID; } set { _ID = value;}}
int _Stuff3;
public int Stuff3 {get { return _Stuff3; } set { _Stuff3 = value;}}
string _Stuff4;
public string Stuff4 {get { return _Stuff4; } set { _Stuff4 = value;}}
public ClassTypeB()
{
this.ID = Guid.NewGuid();
}
}
}
Using only .NET 2.0 methods:
class Foo
{
public Guid Guid { get; }
}
List<Foo> GetFooSubset(List<Foo> foos, List<Guid> guids)
{
return foos.FindAll(foo => guids.Contains(foo.Guid));
}
If your classes don't implement a common interface, you'll have to implement GetFooSubset for each type individually.
I'm not sure that I fully understand what you want, but you can use linq to select out the matching items from the lists as well as sorting them. Here is a simple example where the values from one list are filtered on another and sorted.
List<int> itemList = new List<int>() { 9,6,3,4,5,2,7,8,1 };
List<int> filterList = new List<int>() { 2, 6, 9 };
IEnumerable<int> filtered = itemList.SelectMany(item => filterList.Where(filter => filter == item)).OrderBy(p => p);
I haven't had a chance to use AutoMapper yet, but from what you describe you wish to check it out. From Jimmy Bogard's post:
AutoMapper conventions
Since AutoMapper flattens, it will
look for:
Matching property names
Nested property names (Product.Name
maps to ProductName, by assuming a
PascalCase naming convention)
Methods starting with the word “Get”,
so GetTotal() maps to Total
Any existing type map already
configured
Basically, if you removed all the
“dots” and “Gets”, AutoMapper will
match property names. Right now,
AutoMapper does not fail on mismatched
types, but for some other reasons.
I am not totally sure what you want as your end results, however....
If you are comparing the properties on two different types you could project the property names and corresponding values into two dictionaries. And with that information do some sort of sorting/difference of the property values.
Guid newGuid = Guid.NewGuid();
var classA = new ClassA{Id = newGuid};
var classB = new ClassB{Id = newGuid};
PropertyInfo[] classAProperties = classA.GetType().GetProperties();
Dictionary<string, object> classAPropertyValue = classAProperties.ToDictionary(pName => pName.Name,
pValue =>
pValue.GetValue(classA, null));
PropertyInfo[] classBProperties = classB.GetType().GetProperties();
Dictionary<string, object> classBPropetyValue = classBProperties.ToDictionary(pName => pName.Name,
pValue =>
pValue.GetValue(classB, null));
internal class ClassB
{
public Guid Id { get; set; }
}
internal class ClassA
{
public Guid Id { get; set; }
}
classAPropertyValue
Count = 1
[0]: {[Id, d0093d33-a59b-4537-bde9-67db324cf7f6]}
classBPropetyValue
Count = 1
[0]: {[Id, d0093d33-a59b-4537-bde9-67db324cf7f6]}
Thist should essentially get you what you want - but you may be better of using linq
class T1
{
public T1(Guid g, string n) { Guid = g; MyName = n; }
public Guid Guid { get; set; }
public string MyName { get; set; }
}
class T2
{
public T2(Guid g, string n) { ID = g; Name = n; }
public Guid ID { get; set; }
public string Name { get; set; }
}
class Test
{
public void Run()
{
Guid G1 = Guid.NewGuid();
Guid G2 = Guid.NewGuid();
Guid G3 = Guid.NewGuid();
List<T1> t1s = new List<T1>() {
new T1(G1, "one"),
new T1(G2, "two"),
new T1(G3, "three")
};
List<Guid> filter = new List<Guid>() { G2, G3};
List<T1> filteredValues1 = t1s.FindAll(delegate(T1 item)
{
return filter.Contains(item.Guid);
});
List<T1> filteredValues2 = t1s.FindAll(o1 => filter.Contains(o1.Guid));
}
}

Categories

Resources