var EXPEarners =
from victor in ins.BattleParticipants
where victor.GetComponent<TotalEXP>() != null
select victor;
foreach (GameObject victor in EXPEarners)
{
victor.GetComponent<TotalEXP>().value += EXPGain;
}
I'm new to LINQ and I would like some help. Is there a way to combine these two blocks of code so I don't have to call GetComponent() twice? (I'm using Unity.) Perhaps introduce a temporary variable and use a foreach loop instead? But the whole purpose of using LINQ was to avoid the foreach.
Also, is there a way to inject methods in between the LINQ statements, like call a void method before I select the final result, in case I want to do something "in between?"
There are a number of ways you could do this, but one small alteration to your query would get you to a single call:
First, get rid of the null check and simply return a map of victor and component:
var EXPEarners =
from victor in ins.BattleParticipants
select new {
victor,
component = victor.GetComponent<TotalEXP>()
};
Then, loop over each pair, adding the experience points if the component isn't null:
foreach (var participant in EXPEarners)
{
// can do something with participant.victor here
if (participant.component != null)
participant.component.value += EXPGain;
}
You could of course shorten this code up quite a bit, but if you do need to do something in between, you have the opportunity.
You could try this alternative:
// Dosomething for every item in the list
ins.BattleParticipants.All(gameObject => Reward(gameObject, EXPGain));
Then you write a method to perform "Reward", which can be as complex as you like
static bool Reward(GameObject gameObject, int EXPGain)
{
TotalEXP exp = gameObject.GetComponent<TotalEXP>();
if (exp != null)
{
exp.value += EXPGain;
return true;
}
return false;
}
And if you want, you can chain these, so for example you can also call a "Bonus" for all those you rewarded (where Reward returned true)
// Reward all EXPGain in the list then give them a Bonus
ins.BattleParticipants.Where(gameObject => Reward(gameObject, EXPGain)).All(gameObject => Bonus(gameObject, BONGain));
Then you write a method to perform "Bonus"
static bool Bonus(GameObject gameObject, int BONGain)
{
SomeOther soc = gameObject.GetComponent<SomeOther>();
if (soc != null)
{
soc.value += BONGain;
return true;
}
return false;
}
If you only want to increment TotalEXP value and you don't use a retrived GameObject somewhere else you can use let and retrive the collection of TotalEXP:
var TotalEXPs =
from victor in ins.BattleParticipants
let component = victor.GetComponent<TotalEXP>()
where component != null
select component;
foreach (TotalEXP expin TotalEXPs)
{
exp.value += EXPGain;
}
Otherwise, you can see #Cᴏʀʏ answer where you can retrive GameObject and it TotalEXP
Try searching for the "let" statement on LINQ. Maybe it can help you.
Related
Just wondering why a Select call won't execute if it's called inside of an extended method?
Or is it maybe that I'm thinking Select does one thing, while it's purpose is for something different?
Code Example:
var someList = new List<SomeObject>();
int triggerOn = 5;
/* list gets populated*/
someList.MutateList(triggerOn, "Add something", true);
MutateList method declaration:
public static class ListExtension
{
public static IEnumerable<SomeObject> MutateList(this IEnumerable<SomeObject> objects, int triggerOn, string attachment, bool shouldSkip = false)
{
return objects.Select(obj =>
{
if (obj.ID == triggerOn)
{
if (shouldSkip) shouldSkip = false;
else obj.Name += $" {attachment}";
}
return obj;
});
}
}
The solution without Select works. I'm just doing a foreach instead.
I know that the Select method has a summary saying: "Projects each element of a sequence into a new form." But if that were true, then wouldn't my code example be showing errors?
Solution that I used (Inside of the MutateList method):
foreach(SomeObject obj in objects)
{
if (obj.ID == triggerOn)
{
if (shouldSkip) shouldSkip = false;
else obj.Name += $" {attachment}";
}
});
return objects;
Select uses deferred execution, meaning that it does not actually execute until you try to iterate over the results, with a ForEach, or using Linq methods that require the actual results like ToList or Sum.
Also, it returns an iterator, it does not run on the items in-place, but you're not capturing the return value in your calling code.
For those reasons - I would recommend not using Select to mutate the object in the list. You're just wrapping a ForEach call in a less clean way. I would just use ForEach within the method.
I am exploring if there is a way to check if any of the values present in an array are present in a table without using a loop.
So even if 1 value is present in the table the method should return true, if none of the values are present then it should return the default false.
I am thinking like the below, below is not a working code just an idea.. so wanted to check if there is an efficient way to achieve this.
public bool CheckTable(string strList)
{
bool IsPresent = False;
String[] Arr = strList.split(',');
foreach(string str in Arr)
{
var x = DBContext.TableEmployees.where(e => e.Location == str).select(e => e.Location);
if(x == null)
{
isPresent = false;
}
else
{
isPresent = true;
}
}
}
Like this:
public bool CheckTable(string strList)
{
string[] strs = strList.Split(',');
return DBContext.TableEmployees.Any(e => strs.Contains(e.Location));
}
Take a read of https://www.microsoftpressstore.com/articles/article.aspx?p=2225065&seqNum=4 for more background on IN/EXISTS and how LINQ expressions are mapped to them.
When working with LINQ it's always wise to be mindful that if your expression can not be understood and translated completely into sql it might not run entirely at the server. Sometimes LINQ to sql will download data and rummage through it locally - the "store execution" section of https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/query-execution has some info (the whole document is worth reading, actually - deferred execution is a common gotcha too)
I have a List<Email>() and my Email object looks like this:
public class Email
{
public string EmailAddress { get; set; }
public bool IsPrimary { get; set; }
}
When I add a new email address that is set as primary, I want to set all the others as non-primary. I currently handle this using a foreach. Can I handle this using LINQ?
My current code is:
foreach (var item in emails)
{
if(item.EmailAddress.ToLower() != newEmailAddress.ToLower() && item.IsPrimary)
item.IsPrimary = false;
}
Linq queries collections, it doesn't modify them. The only spot in this equation that linq would come into play is actually making it a part of the enumeration - filtering the collection you're iterating over rather than doing an if statement inside it.
foreach (var item in emails.Where(e => e.IsPrimary && !e.EmailAddress.Equals(newEmailAddress, StringComparison.InvariantCultureIgnoreCase)))
{
item.IsPrimary = false;
}
EDIT: I didn't originally include it as it's not LINQ and that's what the question is about, but as mentioned in the comments on your question List<T> does include a ForEach method.
It would look like this:
emails.ForEach(item =>
{
item.IsPrimary = item.IsPrimary && item.EmailAddress.Equals(newEmailAddress, StringComparison.InvariantCultureIgnoreCase);
});
LINQ is intended for querying and not modification. Having said that, there is a List.ForEach operator, but with no increase in readability most of the time.
Having said that, I personally prefer not having side effect causing code that modifies the collection but I am not opposed to modifying the objects in the collection.
Add an extension method on IEnumerable to encapsulate the foreach loop:
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) {
foreach (var s in source)
action(s);
}
Then you can re-write your code as follows:
emails.Where(item => item.IsPrimary && !item.EmailAddress.Equals(newEmailAddress, StringComparison.InvariantCultureIgnoreCase))
.ForEach(item => item.IsPrimary = false);
(Thanks to #McAden for the better string comparison I always forget.)
However, since you are creating a race condition anyway, if practical I would suggest reversing your order of operations:
// before adding newEmailAddress
emails[emails.FindIndex(item => item.IsPrimary)].IsPrimary = false; // add error handling if it is possible no `IsPrimary` exists.
// now assign the newEmailAddress and set that item.IsPrimary to true
You can easily do that, but you should not as no one would expect LINQ code to modify the items in the collection.
emails
.Where(item =>
(item.EmailAddress.ToLower() != newEmailAddress.ToLower() && item.IsPrimary)
.Select(item => { item.IsPrimary = false; return true;})
.All();
Note that since LINQ queries are actually executed when result is enumerated you need something that will actually enumerate result. I.e. .All() call.
What would happen after you write this code - someone (or you in a week) remove that stupid and pointless .All() call at the end and things will be somewhat
ok, but modification no longer happen, person will spend a day sorting it out and then use some words to describe author of the code. Don't go there.
I have this code which i want to change:
foreach (DirectoryInfo path in currDirs) {
if (!newDirs.Contains(path)) {
MyLog.WriteToLog("Folder not Found: "+path.Name + "in New Folder. ",MyLog.Messages.Warning);
currNoPairs.Add(path);
}
}
In the If part i don't want to check the path i want to check the path.Name.
So how can i use the Contains method on the properties.
the goal is to sort out all folders that have not the same name in the list of Current Directory List and New Directory List.
See - IEnumerable<T>.Contains with predicate
Those functions that take "predicates" (boolean functions that signify a match) will let you do more complex checks. In this case, you can use them to compare sub-properties instead of the top-level objects.
The new code will look something like this:
foreach (DirectoryInfo path in currDirs) {
if (!newDirs.Any(newDir => newDir.Name == path.Name)) {
// TODO: print your error message here
currNoPairs.Add(path.Name);
}
}
In reply to your comment:
Okay i understood, but whats the diffrence between any and contains then?
List<T>.Contains
This method goes through each item in the list, seeing if that item is equal to the value you passed in.
The code for this method looks a little like this (simplified here for illustration):
for(var item in yourList) {
if(item.Equals(itemYouPassedIn) {
return true;
}
}
return false;
As you see, it can only compare top-level items. It doesn't check sub-properties, unless you are using a custom type that overrides the default Equals method. Since you're using the built in DirectoryInfo types, you can't override this Equals behavior without making a custom derived class. Since there's easier ways to do this, I wouldn't recommend this approach unless you need to do it in a ton of different places.
IEnumerable<T>.Any
This method goes through each item in the list, and then passes that item to the "predicate" function you passed in.
The code for this method looks a little like this (simplified for illustration):
for(var item in yourList) {
if(isAMatch(item)) { // Note that `isAMatch` is the function you pass in to `Any`
return true;
}
}
return false;
Your predicate function can be as complicated as you want it to be, but in this case, you'd just use it to check if the sub-properties are equal.
// This bit of code defines a function with no name (a "lambda" function).
// We call it a "predicate" because it returns a bool, and is used to find matches
newDir => newDir.Name == path.Name
// Here's how it might look like if it were defined as a normal function -
// this won't quite work in reality cause `path` is passed in by a different means,
// but hopefully it makes the lambda syntax slightly more clear
bool IsAMatch(DirectoryInfo newDir) {
return newDir.Name == path.Name;
}
Since you can customize this predicate every place that you use it, this could be a better tactic. I'd recommend this style until you are doing this exact check in a bunch of places in your code, in which case a custom class might be better.
Here is how you check for property Any
foreach (DirectoryInfo path in currDirs) {
if (!newDirs.Any(dir => dir.FullName == path.FullName)) {
MyLog.WriteToLog("Folder not Found: "+path.Name + "in New Folder. ",MyLog.Messages.Warning);
currNoPairs.Add(path);
}
}
And by the way, your code could be written in a better way like this
var currDirsConcrete = currDirs.ToArray();
var pathsNotFound = "Following paths were not found \r\n " + string.Join("\r\n", currDirsConcrete.Where(d => d.FullName != path.FullName).ToArray());
var pathsFound = currDirsConcrete.Where(d => d.FullName == path.FullName).ToArray();
MyLog.WriteToLog(pathsNotFound, MyLog.Messages.Warning);
Note: You can skip the first line currDirsConcrete if your currDirs is already an array or a list. I did this to avoid redetermining the enumerable.
I would use linq with except and implement a DirComparator
List<DirectoryInfo> resultExcept = currDirs.Except(newDirs, new DirComparator()).ToList();
Here the IEqualityComparer<DirectoryInfo>:
public class DirComparator : IEqualityComparer<DirectoryInfo> {
public bool Equals(DirectoryInfo x, DirectoryInfo y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.Name.equals(y.Name);
}
public int GetHashCode(DirectoryInfo dir)
{
//Check whether the object is null
if (Object.ReferenceEquals(dir, null)) return 0;
//Get hash code for the Name field if it is not null.
return dir.Name == null ? 0 : dir.Name.GetHashCode();
}
}
you could also use intersect if you want it the other way around.
I would like to know:
What technic is that code?
Can you rewrite that code to make it more readable, because I do
not completely understand its meaning.
Paragraph para = CaretPosition.Paragraph;
var matchedRun = para.Inlines.FirstOrDefault(inline =>
{
Run run = inline as Run;
return (run != null && run.Text.EndsWith(inputText));
}) as Run;
if (matchedRun != null)
{
}
I'd say a more readable version would be:
var matchedRun = para.Inlines
.OfType<Run>()
.FirstOrDefault(r => r.Text.EndsWith(intputText));
OfType filters the input sequence on the given type (Run) and FirstOrDefault finds the first Run instance whose Text property ends with the given input (or null if none was found).
It's Linq. Do you know the "var" keyword? It's a type that the compiler knows but the programmer doesn't want to write.
The re-written code without using Linq is
Paragraph para = CaretPosition.Paragraph;
Run matchedRun = null;
foreach (var inl in para.Inlines)
{
Run run = inl as Run;
if( (run != null) && run.Text.EndsWith(inputText))
{
matchedRun = run;
break;
}
}
if (matchedRun != null)
{
}
Note that I converted "inline" to "inl". It's not a keyword in C# but Stackoverflow makes it look like one.
Also note that it's even LESS readable once you get accustomed to Linq!
This code appears to be related to the RichTextBox class in the .NET Framework.
The CaretPosition.Paragraph.Inlines is a collection of "Inlines" that make up the body of the paragraph.
The code is basically looking for any Inlines that are of type Run, and setting the value of matchedRun to that first instance if there are any. The FirstOrDefault method is simply a convenient way to look inside a collection of objects and retrieve the first element or a default value if the collection is empty.
As far as readability, if you are familiar with LINQ syntax, it isn't too bad to wade through that code, though I do personally find the example code provided by Lee to be a bit more readable - mostly because of the use of another LINQ expression: OfType
it's called "THE MIGHTY LINQ TECHNIQUE" :)
Jokes apart
it is Linq method to get the First element from the collection or return default value.
var matchedRun = para.Inlines.FirstOrDefault(inline =>{ Run run = inline as Run; return (run != null && run.Text.EndsWith(inputText));}) as Run;
=> is called the Lambda techique for shorthand delagate decalaration
so you can read it as
Find 1st object from the para.Inlines collection where that object EndsWith some user suplied value or retrun default value if no match found
if you don't want to use this technique which actually reduces lot of code so you can try below equibvalent code
Paragraph para = CaretPosition.Paragraph;
var matchedRun = null;
foreach (var inl in para.Inlines)
{ Run run = inl as Run;
if ((run != null) && run.Text.EndsWith(inputText))
{ matchedRun = run; break; }
}
Now you can decide which is better to write