LINQ stands for Language Integrated Query. Basically this is used/allows us to filter through data(arrays and lists).
Data filtering on Lists and Arrays can be done using LINQ. Basically this can avoid using foreach/for loops and can be written in one simple line.
Let me show that with an example. But before using LINQ, we need to import this library
using system.Linq in unity.
Now for example i have movieNames list and i need to search a movie name from that string list.
[SerializeField] List<string> movies = new List<string>();
[SerializeField] string toSearch;
Normally what do i do?
I loop through entire movieName list and compare each element with our toSearch string.
foreach (var item in movies)
{
if (toSearch.Equals(item))
Debug.Log("String Found");
else
Debug.Log("String NOT Found");
}
Ok now lets do the same thing using LINQ.
ANY:
LINQ has a function called "Any". "Any" is used to check if at least one of the elements of list/array satisfies a given condition or not. If any of the element satisfies a given condition it returns true or else it returns false.
Is is also used to check if a collection contains some data or not. Like if it contains data it returns true or else false. So basically Any has 2 overloaded methods.
public static bool Any<TSource>(this IEnumerable<TSource> source);
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
For example the above foreach loop can be just written in one line.
var isFound = movies.Any((item) => item.Equals(toSearch));
if (isFound)
Debug.Log("String Found - via LINQ");
else
Debug.Log("String NOT Found - via LINQ");
item variable in 'ANY' function is like "item" variable used in foreach. It holds the current holding element in the loop. Basically i am using the 2nd overloaded method for "ANY" to check for search element result.
Typically when using Linq we use universal data type VAR.
NOTE: Any return true, if any one of the element satisfies the given condition. Let suppose there are 5,6,8,3 in a list and in 'Any' condition we write i > 7. Because one element that is 8, is greater than 7, this "Any" true.
Example program:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class LINQ_ANY_FUNCTION : MonoBehaviour
{
[SerializeField] List<Student> _studentInfo = new List<Student>();
[SerializeField] string _studentNameToSearch;
void Start()
{
if(_studentInfo.Any(t => t._studentName == _studentNameToSearch))
{
Debug.Log("Student Available");
}
else
{
Debug.Log("Student NOT Available");
}
}
}
[System.Serializable]
public class Student
{
public int _studentID;
public string _studentName;
}
CONTAINS:-
Contains method is used to check if a collection(array/list) contains a specific element or not.
Example:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class LINQ_Contains : MonoBehaviour
{
[SerializeField] List<string> movies = new List<string>();
[SerializeField] string toSearch;
void Start()
{
if(movies.AsEnumerable().Contains(toSearch))
{
Debug.Log("YES Contains");
}
else
{
Debug.Log("No Donot Contain");
}
}
}
Contains works great for basic datatypes such as int, string, float etc as shown in the above example. But the problem comes with the complex data types.
Try to paste given code in unity. Provide those respective inspector values and execute the code:
public class LINQ_Contains_WithoutUsingComparer : MonoBehaviour
{
[SerializeField] List<StudentDetials> studentDetails = new List<StudentDetials>();
[SerializeField] StudentDetials toCompare;
void Start()
{
if (studentDetails.AsEnumerable().Contains(toCompare))
{
Debug.Log("Yes Exists");
}
else
Debug.Log("Nope Doesn't Exist");
}
}
[System.Serializable]
public class StudentDetials
{
public int ID;
public string name;
public int age;
}
Even if you give same object values, the code comparison fails that is because,
for complex data types Compare doen't compare all the values. It just sees and compare the object reference NOT THE VALUES INSIDE.
So then how to we tackle this problem?
We use the 2nd overloaded method for 'contains', which uses the interface IEqualityComparer.
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value);
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer);
IEqualityComparer will be used in many places in our discussion. We create a class using this interface and tell unity how to compare 2 complex datatypes. And then we pass this object to the Contains function.
public class LINQ_Contains_UsingComparer : MonoBehaviour
{
[SerializeField] List<StudentDetials> studentDetails = new List<StudentDetials>();
[SerializeField] StudentDetials toCompare;
//Using Comparer
StudentComparer comparer;
void Start()
{
comparer = new StudentComparer();
if (studentDetails.AsEnumerable().Contains(toCompare, comparer))
{
Debug.Log("Yes Exists");
}
else
Debug.Log("Nope Doesn't Exist");
}
}
[System.Serializable]
public class StudentDetials
{
public int ID;
public string name;
public int age;
}
public class StudentComparer : IEqualityComparer<StudentDetials>
{
public bool Equals(StudentDetials x, StudentDetials y)
{
return (x.ID == y.ID && x.name.ToLower() == y.name.ToLower() && x.age == y.age);
}
public int GetHashCode(StudentDetials obj)
{
throw new System.NotImplementedException();
}
}
This can give you desired output.
Further in this blog, There are many places where i use comparer syntax for other functions. The basic functionality is same.
Distinct :-
This function returns the distinct IEnumerable as an output.
using System.Linq;
public class LINQ_Distinct_BasicPrimitiveTypes : MonoBehaviour
{
[SerializeField] List<string> movieNames = new List<string>();
void Start()
{
Debug.Log("Giving Distinct elements by removing duplicates");
var distinctMovieNames = movieNames.Distinct();
foreach (var item in distinctMovieNames)
{
Debug.Log(item);
}
}
}
Similar to contains, Distinct also works fine with primitive data types. But for complex data types this doesn't work as expected.
For example run this code and check for yourself in unity.
using System.Linq;
public class LINQ_Distinct_WithoutUsingComparer : MonoBehaviour
{
[SerializeField] List<StudentDetails_Distinct> studentDetails = new List<StudentDetails_Distinct>();
void Start()
{
var distinct_studentsDetails = studentDetails.Distinct();
foreach (var item in distinct_studentsDetails)
{
Debug.Log("ID:" + item.id + " " + "name:" + item.name + " " + "age:" + item.age);
}
}
}
[System.Serializable]
public class StudentDetails_Distinct
{
public int id;
public string name;
public int age;
}
Even if you give similar entries in studentDetails list, this doen't consider them similar because it only checkes whether two object references are equal and not the individual property values of the complex object.
To do this we need to implement a class with a IEqualityComparer<> interface and then pass this object as a parameter to Distinct() function.
So, Distinct has 2 overloaded methods,
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
Of you see the second parameter, it has comparer object passed.
Now for Contains() function, we have over ridded and wrote our own Equals() function for IEqualityComparer interface. Here we need to overrirde GetHash() function. We have to define the way how each entry should define its own hash value, which is used by Distinct() function to compare 2 similar entries.
Example:
using System.Linq;
public class LINQ_Distinct_UsingComparer : MonoBehaviour
{
[SerializeField] List<StudentDetails_Distinct> studentDetails = new List<StudentDetails_Distinct>();
void Start()
{
StudentComparer_Distinct comparer = new StudentComparer_Distinct();
var distinct_studentsDetails = studentDetails.Distinct(comparer);
foreach (var item in distinct_studentsDetails)
{
Debug.Log("ID:" + item.id + " " + "name:" + item.name + " " + "age:" + item.age);
}
}
}
public class StudentComparer_Distinct : IEqualityComparer<StudentDetails_Distinct>
{
public bool Equals(StudentDetails_Distinct x, StudentDetails_Distinct y)
{
return ((x.id == y.id) && (x.name.ToString() == y.name.ToString()) && (x.age == y.age));
}
public int GetHashCode(StudentDetails_Distinct obj)
{
int idHash = obj.id.GetHashCode();
int nameHash = obj.name.GetHashCode();
int ageHash = obj.age.GetHashCode();
return idHash * nameHash * ageHash;
}
}
[System.Serializable]
public class StudentDetails_Distinct
{
public int id;
public string name;
public int age;
}
This gives us the desired output. :)
WHERE :-
Using this, we can get all entries which satisfies where condition into a separate collection.
using System.Linq;
public class LINQ_Where : MonoBehaviour
{
[SerializeField] bool _primitiveDataTypes;
[SerializeField] bool _complexDataTypes;
[SerializeField] List<int> age = new List<int>();
[SerializeField] List<StudentDetials> studentDetails = new List<StudentDetials>();
void Start()
{
if (_primitiveDataTypes)
{
var age_filer = age.Where(t => t > 20);
Debug.Log("Age greater than 20 entries are: ");
foreach (var item in age_filer)
{
Debug.Log(item);
}
}
if(_complexDataTypes)
{
var studentFilter = studentDetails.Where(t => t.age > 20);
Debug.Log("Age greater than 20 entries are: ");
foreach (var item in studentFilter)
{
Debug.Log("Name: " + item.name + " Age: " + item.age);
}
}
}
}
[System.Serializable]
public class StudentDetials
{
public int ID;
public string name;
public int age;
}
ORDER BY :-
Order by is used to sort the data in ascending order. The most important point that you need to keep in mind is this method is not going to change the data rather it is just changing the order of the data.
using System.Linq;
public class LINQ_OrderBy : MonoBehaviour
{
[SerializeField] bool _primitiveDataTypes;
[SerializeField] bool _complexDataTypes;
[SerializeField] bool _usingAlongWithWhere;
[SerializeField] List<int> age = new List<int>();
[SerializeField] List<StudentDetials> studentDetails = new List<StudentDetials>();
void Start()
{
if (_primitiveDataTypes)
{
var orderbyList = age.OrderBy(t => t);
Debug.Log("Ordered by value:");
foreach (var item in orderbyList)
{
Debug.Log(item);
}
}
if(_complexDataTypes)
{
var studentFilter = studentDetails.OrderBy(t => t.age);
Debug.Log("Order by Age: ");
foreach (var item in studentFilter)
{
Debug.Log("Name: " + item.name + " Age: " + item.age);
}
}
}
}
we can also use orderby along with where -
example:
var whereOrderbyFiler = studentDetails.Where(t => t.age > 22).OrderBy(t => t.age);
Debug.Log("Order by Age with where filter: ");
foreach (var item in whereOrderbyFiler)
{
Debug.Log("Name: " + item.name + " Age: " + item.age);
}
Comments