I have asked this question about using the a Linq method that returns one object (First, Min, Max, etc) from of a generic collection.
I now want to be able to use linq's Except() method and I am not sure how to do it. Perhaps the answer is just in front on me but think I need help.
I have a generic method that fills in missing dates for a corresponding descriptive field. This method is declared as below:
public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection, string datePropertyName, string descriptionPropertyName)
{
Type type = typeof(T);
PropertyInfo dateProperty = type.GetProperty(datePropertyName);
PropertyInfo descriptionProperty = type.GetProperty(descriptionPropertyName);
...
}
What I want to accomplish is this. datePropertyName is the name of the date property I will use to fill in my date gaps (adding default object instances for the dates not already present in the collection). If I were dealing with a non-generic class, I would do something like this:
foreach (string description in descriptions)
{
var missingDates = allDates.Except(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
...
}
But how can I do the same using the generic method FillInMissingDates with the dateProperty and descriptionProperty properties resolved in runtime?
I think the best way would be to define an interface with all of the properties that you want to use in your method. Have the classes that the method may be used in implement this interface. Then, use a generic method and constrain the generic type to derive from the interface.
This example may not do exactly what you want -- it fills in missing dates for items in the list matching a description, but hopefully it will give you the basic idea.
public interface ITransactable
{
string Description { get; }
DateTime? TransactionDate { get; }
}
public class CompletedTransaction : ITransactable
{
...
}
// note conversion to extension method
public static void FillInMissingDates<T>( this IEnumerable<T> collection,
string match,
DateTime defaultDate )
where T : ITransactable
{
foreach (var trans in collection.Where( t => t.Description = match ))
{
if (!trans.TransactionDate.HasValue)
{
trans.TransactionDate = defaultDate;
}
}
}
You'll need to Cast your enumeration to ITransactable before invoking (at least until C# 4.0 comes out).
var list = new List<CompletedTransaction>();
list.Cast<ITransactable>()
.FillInMissingDates("description",DateTime.MinValue);
Alternatively, you could investigate using Dynamic LINQ from the VS2008 Samples collection. This would allow you to specify the name of a property if it's not consistent between classes. You'd probably still need to use reflection to set the property, however.
You could try this approach:
public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection,
Func<T, DateTime> dateProperty, Func<T, string> descriptionProperty, string desc)
{
return collection.Except(collection
.Where(d => descriptionProperty(d) == desc))
.Select(d => dateProperty(d));
}
This allows you to do things like:
someCollection.FillInMissingDates(o => o.CreatedDate, o => o.Description, "matching");
Note that you don't necessarily need the Except() call, and just have:
.. Where(d => descriptionProperty(d) != desc)
foreach (string description in descriptions)
{
var missingDates = allDates.Except<YourClass>(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
}
In fact, almost all LINQ extension in C# have a generic possible value. (Except and Except)
If you're going to identify the property to be accessed by a string name, then you don't need to use generics. Their only purpose is static type safety. Just use reflection to access the property, and make the method work on a non-generic IEnumerable.
Getting Except result with multiple properties working with custom data class is not allowed.
You have to use it like this: (given in msdn 101 LINQ Samples)
public void Linq53()
{
List<Product> products = GetProductList();
List<Customer> customers = GetCustomerList();
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
var productOnlyFirstChars = productFirstChars.Except(customerFirstChars);
Console.WriteLine("First letters from Product names, but not from Customer names:");
foreach (var ch in productOnlyFirstChars)
{
Console.WriteLine(ch);
}
}
Having the key, you can handle your data accordingly :)
Related
I have the following generic method that I need to be able to perform a LINQ Where query in:
public static List<T> GetItems<T>(Guid parentId = new Guid()) where T : new()
{
var db = new SQLiteConnection(_dbPath);
List<T> result;
if (parentId != Guid.Empty)
{
result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
}
else
{
result = db.Table<T>().ToList();
}
db.Close();
return result;
}
The compiler doesn't like the following line
result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
error: cannot resolve 'ParentId'
Is it possible to use generics in this way in a LINQ query? Note that object of type T will always have a ParentId property.
You should concretize T parameter with some interface which will include required values. Also, you should add this interface to all types that contains this field or base type for its classes.
public interface IHierarchy
{
public Guid ParentId { get; }
}
public static List<T> GetItems<T>(Guid parentId = new Guid())
where T : IHierarchy, new()
{
var db = new SQLiteConnection(_dbPath);
List<T> result;
if (parentId != Guid.Empty)
{
result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
}
else
{
result = db.Table<T>().ToList();
}
db.Close();
return result;
}
If you have 2 types of entities and the first contains required values and the second does not, then you can have two overloads for this scenario.
You used a generic type and the compiler don't know which entity are you going to use.
Just use reflection feature of .NET language.
result = db.Table<T>().Where(i => i.GetType().GetProperty("ParentId").GetValue(src, null).Equals(parentId)).ToList();
The problem is that in your code you assume that every T has a GUID property ParentId, while in fact you only required that every T has a default constructor. You need to require that every T has a ParentId.
You could do this by requiring that every T implements some interface. Like other answers suggest, however this is quite a nuisance, because for every class that you want to use this function for you'll need to implement this interface.
The function Enumerable.Where seems to be able to do the same job, without requiring any interface from the input items. So let's use the same method:
As input we tell which property to use (in your case ParentId) and with which value to compare (in your case parentId).
The only requirement we have is that we must be able to compare ParentId with parentId: it should be IEquatable
public List<T> GetItems<T, TKey>(Func<T, TKey> keySelector, Tkey value)
TKey : IEquatable<TKey>,
{
...
result = db.Table<T>()
.Where(t => keySelector(t).Equals(value))
.ToList();
}
Usage:
Guid value = ...
var items = GetItems<MyClass, Guid>(item => item.ParentId, value);
This function will also work with other classes and other properties:
int studentId = ...
var students = GetItems<Student, int>(student => student.Id, studentId);
var otherStudents = GetItems<Student, string>(student => student.Name, "John");
Two side remarks:
- you use new Guid() to define some default Guid. It is faster to use Guid.Empty
- You will not create new items of type T. They are already in your dbContext.Table. Therefore you don't need new().
- However, if your Table requires that T is a class then you should require that. See your Table definition:
where T : class
I have a generic list which I want to order by two properties, priority and then by description to fill a drop down list.
I know that when I now exactly the type of the object list I can do
list = list.Orderby(x=>x.property1).ThenOrderBy(x=>x.property2).
My question is how can I check if the property1 and property2 exist on the object and then sort my list based on those properties.
Because you are using a generic list, the compiler will check that for you.
For example: if you write code like
List<Object> list = new List<Object>();
var newlist = list.Orderby(x=>x.property1).ThenOrderBy(x=>x.property2);
you'll get a compiler error on property1 & property2 because the compiler won't find these properties on the Object type.
If you want to support different types that each should have those 2 properties, the correct way would be to create an interface with those 2 properties, let each of the types you want to support implement that interface and then use a constraint on T like Arturo proposed.
Something like
interface ICanBeSorted
{
string property1 {get;}
string property2 {get;
}
public List<T> MySortMethod(List<T> list) where T : ICanBeSorted
{
return list.OrderBy(x=>x.property1).ThenOrderBy(x=>x.property2);
}
This method will be able to sort all types that implement interface ICanBeSorted.
You can wrap the selector in a try-catch block :
Func<dynamic, dynamic> DynamicProperty(Func<dynamic, dynamic> selector)
{
return x =>
{
try
{
return selector(x);
}
catch (RuntimeBinderException)
{
return null;
}
};
}
Usage :
var sorted = list
.OrderBy(DynamicProperty(x => x.property1))
.ThenBy(DynamicProperty(x => x.property2));
well, the way you are trying to do it is a bad idea. Try using if-else or switch case first and then put your code to order them the way you want accordingly.
I have 2 classes and 2 IEnumerable lists of those classes:
public class Values
{
public int Value { get; set; }
public DateTime SomeDate { get; set; }
}
public class Holidays
{
public DateTime holiday { get; set; }
}
IEnumerable<Values> values;
IEnumerable<Holidays> holidays;
Next, I am trying to select those 'values' where 'someDate' is not in 'holidays'
var query = values.Where(x=> !holidays.Contains(x.someDate));
However, this gives me the error of IEnumerable<Holidays> does not contain a definition of 'Contains'.
System.Linq is already added in the usings.
I believe this has to do something with the collections, but am not able to figure what.
When you use Contains, the object you're looking for must match the type T of the IEnumerable<T>. Thus, you cannot search IEnumerable<A> for a contained object of type B since there's no implicit way to compare the two.
As mentioned in other answers, use Any and pass in the comparison yourself.
Alternatively, this is also a case where you could use a Select followed by Contains, although this may be less readable in some cases:
var query = values
.Where(x => !holidays
.Select(h => h.holiday)
.Contains(x.someDate));
As an alternative to what everyone else has suggested already:
var holidayDates = new HashSet<DateTime>(holidays.Select(h => h.holiday));
var query = values.Where(x => !holidayDates.Contains(x.someDate));
In particular, if you have a lot of holidays, this change will make the per-value check much more efficient.
Contains is a LINQ extension that takes (in your case) an instance of Holidays and checks if your enumeration contains that instance (or an instance that Equals the given argument).
You should use Any instead:
var query = values.Where(x=> !holidays.Any(h => h.holiday == x.someDate));
You probably want Any():
var query = values.Where(v => !holidays.Any(h => h.holiday == v.someDate));
You can't compare a Holiday object with a DateTime. Use Any extension method instead:
var query = values.Where(x=> !holidays.Any(e=>e.holiday ==x.someDate));
Or you can also use All extension method:
var query = values.Where(x=> holidays.All(e=>e.holiday !=x.someDate));
This is my function:
private IEnumerable<string> SeachItem(int[] ItemIds)
{
using (var reader = File.OpenText(Application.StartupPath + #"\temp\A_A.tmp"))
{
var myLine = from line in ReadLines(reader)
where line.Length > 1
let id = int.Parse(line.Split('\t')[1])
where ItemIds.Contains(id)
let m = Regex.Match(line, #"^\d+\t(\d+)\t.+?\t(item\\[^\t]+\.ddj)")
where m.Success == true
select new { Text = line, ItemId = id, Path = m.Groups[2].Value };
return myLine;
}
}
I get a compile error,because "myLine" is not a IEnumerable[string] and I don't know how to write IEnumerable[Anonymous]
"Cannot implicitly convert type 'System.Collections.Generic.IEnumerable[AnonymousType#1]' to 'System.Collections.Generic.IEnumerable[string]'"
You cannot declare IEnumerable<AnonymousType> because the type has no (known) name at build time. So if you want to use this type in a function declaration, make it a normal type. Or just modify your query to return a IENumerable<String> and stick with that type.
Or return IEnumerable<KeyValuePair<Int32, String>> using the following select statement.
select new KeyValuePair<Int32, String>(id, m.Groups[2].Value)
I am not necessarily recommending this...
It is a kind of subversion of the type system but you could do this:
1) change your method signature to return IEnumerable (the non generic one)
2) add a cast by example helper:
public static class Extensions{
public static IEnumerable<T> CastByExample<T>(
this IEnumerable sequence,
T example) where T: class
{
foreach (Object o in sequence)
yield return o as T;
}
}
3) then call the method something like this:
var example = new { Text = "", ItemId = 0, Path = "" };
foreach (var x in SeachItem(ids).CastByExample(example))
{
// now you can access the properties of x
Console.WriteLine("{0},{1},{2}", x.Text, x.ItemId, x.Path);
}
And you are done.
The key to this is the fact that if you create an anonymous type with the same order, types and property names in two places the types will be reused. Knowing this you can use generics to avoid reflection.
Hope this helps
Alex
The method signature on SearchItem indicates that the method returns an IEnumerable<string> but the anonymous type declared in your LINQ query is not of type string. If you want to keep the same method signature, you have to change your query to only select strings. e.g.
return myLine.Select(a => a.Text);
If you insist on returning the data selected by your query, you can return an IEnumerable<object> if you replace your return statement with
return myLine.Cast<object>();
Then you can consume the objects using reflection.
But really, if your going to be consuming an anonymous type outside the method that it is declared in, you should define a class an have the method return an IEnumerable of that class. Anonymous types are convenience but they are subject to abuse.
Your function is trying to return IEnumerable<string>, when the LINQ statement you are executing is actually returning an IEnumerable<T> where T is a compile-time generated type. Anonymous types are not always anonymous, as they take on a specific, concrete type after the code is compiled.
Anonymous types, however, since they are ephemeral until compiled, can only be used within the scope they are created in. To support your needs in the example you provided, I would say the simplest solution is to create a simple entity that stores the results of your query:
public class SearchItemResult
{
public string Text { get; set; }
public int ItemId { get; set; }
public string Path { get; set; }
}
public IEnumerable<SearchItemResult> SearchItem(int[] itemIds)
{
// ...
IEnumerable<SearchItemResult> results = from ... select new SearchItemResult { ... }
}
However, if your ultimate goal is not to retrieve some kind of object, and you are only interested in, say, the Path...then you can still generate an IEnumerable<string>:
IEnumerable<string> lines = from ... select m.Groups[2].Value;
I hope that helps clarify your understanding of LINQ, enumerables, and anonymous types. :)
Return a ValueTuple instead of an anonymous class. Ex (using "named tuples")-
(Text: line, ItemId: id, Path: m.Groups[2].Value)
https://learn.microsoft.com/en-us/dotnet/csharp/tuples
Instead of-
new { Text = line, ItemId = id, Path = m.Groups[2].Value }
The ValueTuple is part of C# version 7 and was originally implemented as a separate NuGet package (System.ValueTuple). Starting with .NET 4.7 it is a built-in type. For .NET Core, versions prior to 2.0 required the NuGet package but it is built-in with version 2.0.
The question was asked a long time ago, I hope it helps someone...
"You cannot declare IEnumerable", instead, must convert it to a "custom" IEnumerable:
public class MyString
{
public string String { get; set; } = string.Empty;
}
public IEnumerable<MyString> GetMyStrings(List<string> Strings)
{
var AnonString = from S in Strings group S by S into Grouped select new { String = Grouped.Key };
IEnumerable<MyString> Result = AnonString.Select(x => new MyString() { String = x.String }).ToArray();
return Result;
}
Regards.
this link could be useful for others who end up here
https://idreesdotnet.blogspot.com/2019/08/c-how-to-create-list-of-anonymous-type.html
the first solution (of 6) is delightlfully simple
1: First create the object(s) of anonymous type and then pass it to an array and call ToList() method.
var o1 = new { Id = 1, Name = "Foo" };
var o2 = new { Id = 2, Name = "Bar" };
var list = new[] { o1, o2 }.ToList();
I have a List of a "complex" type - an object with a few string properties. The List itself is a property of another object and contains objects of a variety of types, as shown in this abbreviated class structure:
Customer {
public List<Characteristic> Characteristics;
.
.
.
}
Characteristic {
public string CharacteristicType;
public string CharacteristicValue;
}
I'd like to be able to collect a List of the values of a given type of Characteristics for the current Customer, which I can do in a 2-step process as follows:
List<Characteristic> interestCharacteristics = customer.Characteristics.FindAll(
delegate (Characteristic interest) {
return interest.CharacteristicType == "Interest";
}
);
List<string> interests = interestCharacteristics.ConvertAll<string>(
delegate (Characteristic interest) {
return interest.CharacteristicValue;
}
);
That works fine, but it seems like a long way around. I'm sure I must be missing a simpler way of getting to this list, either by chaining together the FindAll() and Convert() methods, or something else I'm overlooking entirely.
For background, I'm working in .Net 2.0, so I'm limited to the .Net 2 generics, and the Characteristic class is an external dependency - I can't change it's structure to simplify it, and there are other aspects of the class that are important, just not in relations to this problem.
Any pointers or additional reading welcomed.
Here's a generator implementation
public static IEnumerable<string> GetInterests(Customer customer)
{
foreach (Characteristic c in customer.Characteristics)
{
if (c.CharacteristicType == "Interest")
yield return c.CharacteristicValue;
}
}
sadly 3.5 extension methods and lambda are out based on your requirements but for reference here's how to do it:
customer.Characteristics
.Where(c => c.CharacteristicType == "Interest")
.Select(c => c. CharacteristicValue);
I would do some of the work manualy. By doing a FindAll first, and then a Convert, you're looping through your collection twice. It doesn't seem neccessary. If all you want at the end of the day, is a List of CharacteristicValue then just loop through your original collection, and add the CharacteristicValue to a List of each one that matches your criteria. Something like this:
Predicate<Characteristic> criteria = delegate (Characteristic interest)
{
return interest.CharacteristicType == "Interest";
};
List<string> myList = new List<string>();
foreach(Characteristic c in customer.Characteristics)
{
if(criteria(c))
{
myList.Add(c.CharacteristicValue);
}
}
Why not create a Dictionary<string, List<string>>, that way you can add "Interest" as the key, and a list of values as the value. For example:
Customer {
public Dictionary<string, List<string>> Characteristics;
.
.
.
}
...
Characteristics.Add("Interest", new List<string>());
Characteristics["Interest"].Add("Post questions on StackOverflow");
Characteristics["Interest"].Add("Answer questions on StackOverflow");
..
List<Characteristic> interestCharacteristics = Characteristics["Interest"];
Furthermore, if you wanted, you could limit your characteristics to a list of possible values by making it an enum, then use that as the data type of your dictionary's key:
public enum CharacteristicType
{
Interest,
Job,
ThingsYouHate
//...etc
}
then declare your dictionary as:
public Dictionary<CharacteristicType, List<string>> Characteristics;
..
Characteristics.Add(CharacteristicType.Interest, new List<string>());
Characteristics[CharacteristicType.Interest].Add("Post questions on StackOverflow");
Characteristics[CharacteristicType.Interest].Add("Answer questions on StackOverflow");