Home > Software design >  Linq where with strange condition
Linq where with strange condition

Time:10-27

I'm struggling to understand this lines of code but I don't grow out of it.

The instruction is the following

var targetHandles = target.Where(t => true);

that is inside a function with these signature

public static void Merge<T>(ObservableCollection<T> target, ObservableCollection<T> source) where T : DtoBase

Thanks in advance for your answers

Ric

CodePudding user response:

I'm not sure whether your problem is only in the Where of LINQ, or in the where T: DtoBase, so I'll discuss both

Where in a generic method

public static void Merge<T>(ObservableCollection<T> target,
                            ObservableCollection<T> source)
                            where T : DtoBase

The <T> after the identifier of the method Merget<T>, tells us that this is a generic method, which means that you can call it with all kinds of classes, as long as the class meets the where condition:

where T : DtoBase

So you can call this method with any class T, as long as this class is (derived from) DtoBase.

Examples:

class DtoBase {...};
class Derived : DtoBase {...};

ObservableCollection<DtoBase> myBases = ...
ObservableCollection<DtoBase> yourBases = ...

ObservableCollection<Derived> myDeriveds = ...
ObservableCollection<Derived> yourDeriveds = ...

Now you can call method Merge, and replace every Occurence ot T with anything that matches where T : DtoBase. So the following are all valid:

Merge(myBases, yourBases);
Merge(myBases, myBases);
Merge(myDeriveds, yourDeriveds);
Merge(myBases, myDeriveds);

The following won't compile:

Merge(myBases, 4);         // integer 4 is not a DtoBase
Merge("Hello", myBases);   // string is not a DtoBase

Where in a LINQ statement

var targetHandles = target.Where(t => true);

The t in this statement has nothing to do with the T in the generic method.

If you look at the definition of Enumerable.Where you'll see that it is a generic extension method.

public static IEnumerable<TSource> Where<TSource> (
    this IEnumerable<TSource> source,
    Func<TSource,bool> predicate);

Generic is discussed above: Where has a type TSource, comparable with the T we discussed earlier: wherever you see TSource you should replace it with the same actual type.

We also see the keyword this in front of the first parameter. This means that it is an extension method: you can put the first parameter in front of the method, like this:

IEnumerable<Product> products = ...
IEnumerable<Product> cheapProducts = products.Where(product => product.Price < 1);

This is the same as:

IEnumerable<Product> cheapProducts = Enumerable<Product>.Where(products,
    product => product.Price < 1);

So extension method is just some fancy syntactic sugar. It makes it look as if it is a method of Product.

Now the fun part: the product in product => product.Price < 1. product is just an identifier with a well chosen name. I could also have written:

.Where(t => t.Price < 1);

the t => t.Price < 1 is called a lambda expression. It is some shorthand for a function definition: we create a method with input parameter t, which returns t.price < 1. The type of t can be found in the generic:

public static IEnumerable<TSource> Where<TSource> (
    this IEnumerable<TSource> source,
    Func<TSource,bool> predicate);

In my example TSource was a Product, so the first parameter should be IEnumerable<Product>. The return value is also IEnumerable<TSource>.

IEnumerable<Product> products = ...
IEnumerable<Product> cheapProducts = products.Where(...);

predicate is a Func<TSource, bool>. This means: any function, with input TSource (which is in our case a Product), that returns a bool. So if you see somewhere:

Func<int, string, DateTime, Point>

Then it means: any method with three input parameters of type int, string, DateTime (in this order), which returns a Point:

Point MyFunct(int i, string txt, DateTime date) {...}

We've seen that the lambda expression t => t.Price < 1 is a method that takes a Product t as input parameter and returns the value of t.Price < 1, which obviously is a Boolean. Hence, this lambda expression matches Func<TSource,bool>.

IEnumerable<Product> cheapProducts = products.Where(product => product.Price < 1);

So what does this do? It takes every product from the sequence of products, and puts this in the function product => product.Price < 1. In other words: from every product in the sequence of Products, it calculates the Boolean product.Price < 1. It it is true, then we keep it, if not, we don't use it in the sequence returned by the Where.

The effect is, that Where(product => product.Price < 1) returns the sequence of all Products with a value for property Price that is less than 1.

Back to your question

Your Where is a rather strange one.

IEnumerable<Target> targets = ...
targets.Where(t => true);

We know that targets is a sequence of type Target. Hence, we know that the predicate is a method that takes as input one Target, and returns a bool:

bool Predicate(Target t) { return true}

Well, it does return a boolean. In fact, it always returns the same boolean, it doesn't even look at the input parameter t, it always returns true.

Where(t => true) is similar to the following:

foreach(Target target in targets)
{
    if (true) return target;
}

Well, this doesn't do a lot: it returns the complete input sequence:

IEnumerable<Target> targets = ...
var result = targets.Where(t => true);

We know that result has the same elements, in the same order as targets. The statement is not very useful.

Final remark

Whenever you have to define your own identifiers in LINQ statements, try to use plural nouns to identify sequences, use singular nouns to identify elements of the sequences. Try to avoid meaningless identifiers as t =>.

typing less characters is not a good excuse for choosing bad identifiers!

IEnumerable<Customer> customers = ...
IEnumerable<Order> orders = ...

var customersWithTheirOrders = customer.GroupJoin(orders,

    customer => customer.Id,       // from every Customer take the primary key
    order => order.CustomerId,     // from every Order take the foreign key

    (customer, ordersOfThisCustomer) => new
    {
        Id = customer.Id,
        Name = customer.Name,
        Address = customer.Address,

        Orders = ordersOfThisCustomer.Select(order => new
        {
            Date = order.Date,
            Total = order.Total,
        })
        .ToList(),
    })

This is a very big LINQ. You might not be familiar with GroupJoin, but because I used plural and singular nouns, it is not very difficult to read:

In words: We have a sequence of Customers and a sequence of Orders. We GroupJoin the Customers and the Orders. This means, that from every Customer we take the Id, from every Order we take the CustomerId. We try to match them. From every customer with his zero or more Orders, we make one new object.

This new object contains the Id of the Customer, as well as his Name and his Address. From every Order in the sequence of Orders of this Customer, we make one new object. This new object contains the Date and the Total of the Order.

Because I used plural and singular nouns, this textual description corresponds very much with the LINQ. You don't have to wonder what t and x are. Customer and OrdersOfThisCustomer are much easier to understand what they stand for.

CodePudding user response:

The expression source.Where(x => true) will return an IEnumerable containing all the items in source, no matter the value of each item. This is because true is the entire predicate which determines which items should be filtered, and true is always true.

In most cases, such a predicate would be a function of the item (e.g. x > 3), but it does not have to be.

Thereby, target.Where(t => true) returns an IEnumerable<T> containing all the items in target. This way, a separate collection that can be worked with individually (without affecting target) is created.

var targetHandles = target;, on the other hand, would have created a reference to target.

  • Related