- Home /
Some Dictionary LINQ functions not cooperating?
I can't seem to figure out why some of these expressions are not working. I'm getting Null errors. Even foo.Any() gives me a 'cannot be null' error. Why?
The idea with this code is for an AI to find nearby allies, check their Threat List, see if their biggest threat is bigger than its own current threat, then adopt it if so.
Subject is the base class of entities.
Intellect is the AI which contains the lists/dictionaries.
Problematic section of code:
foreach (Intellect buddy in AllyList
.Select(ally => ally.GetComponent<Intellect>())
.Where(buddy => buddy.ThreatList.Any())
.Where(buddy => buddy.ThreatList.Max().Value > myThreat[0].Value))
{
myThreat[0] = buddy.ThreatList.Max();
}
And here is an alternative I tried. They are the same, and both give me the error.
foreach (Subject ally in AllyList)
{
Intellect buddy = ally.GetComponent<Intellect>();
if (buddy.ThreatList.Any())
{
if (buddy.ThreatList.Max().Value > myThreat[0].Value)
{
myThreat[0] = buddy.ThreatList.Max();
}
}
}
This is the error:
ArgumentNullException: Argument cannot be null.
Parameter name: source
System.Linq.Check.Source (System.Object source)
System.Linq.Enumerable.Any[KeyValuePair`2] (IEnumerable`1 source)
Deftly.Intellect.GetHighestThreat () (at Assets/Deftly/Scripts/Intellect.cs:495)
Deftly.Intellect+<ProcessConditions>c__Iterator1.MoveNext () (at Assets/Deftly/Scripts/Intellect.cs:253)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
Deftly.<StateMachine>c__Iterator0:MoveNext() (at Assets/Deftly/Scripts/Intellect.cs:215)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
Deftly.Intellect:Start() (at Assets/Deftly/Scripts/Intellect.cs:141)
The error reports that it is the if (buddy.ThreatList.Any()) line but I've had issues using other methods to find out if there are no elements, like .Count... It does the same error if I use that.
Any idea why this is happening? Could this be a SEO issue? The script seems to run fine and this only occurs (once) in the rare case that one AI can see the player, which triggers these checks, but the other AI(s) cannot and therefore have empty ThreatLists but I assumed the .Any() or .Count > 0 checks would prevent that error.
I'm not familiar with .Any()
. How does it differ from .Count() > 0
?
.Any() returns a bool based on if the source has any elements.
Which is why above, i say they're the same and i tried not using .Any() to see if the issue was related to the LINQ expression.
Answer by Baste · Jun 02, 2015 at 09:06 AM
The any method is documented like this:
public static bool Any<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
...
throws ArgumentNullException if source or predicate is null.
In your case, the predicate is not null, as it's a lambda. Your error log should also be a hint, as it says "Parameter name: source"
This means that your AllyList is null. If Linq had been "normal" methods, you'd have gotten a NullReferenceException, but since they're extension methods, they get re-written into static method calls, and you get the ArgumentNullException instead.
Hmm, I see what you mean but still don't fully understand why its happening.
As far as I can tell, the error seems to happen when an AI has one threat but its neighbor Ally that it queries does not have any threats... however I only see it once in the console so I'm not sure if it is because it stops or if it actually only happens one time... Like for instance the Intellect script hadn't initiated on the Ally before it was queried for threats?
If the actual List is null then that must be true and I'm not really sure how to address the issue. SEO is setup properly for this but it seems like one Intellect is processing entirely before the other can initiate.
Looking over your code again, I kinda messed up. It's not the AllyList that's null, it's the buddy.ThreatList that's null, as it's the one getting called .Any on.
Still, Any() throws the error if it's called on a null-object. I can see two solutions:
1: ensure that the ThreathList is instantiated in Awake, and that this method is not called in any Awake calls. Then you'll be sure that the ThreathList is always instantiated when it's needed.
In general, you should create everything that can be created internally in a class in Awake, and create everything that requires other objects to exist in Start.
2: make the ThreathList lazy. If it's not a public variable that you need to put stuff in from the inspector, you can declare it like this, assu$$anonymous$$g it's a list of Subject:
public List<Subject> ThreathListBacker;
public List<Subject> ThreathList {
get {
if(ThreathListBacker == null)
ThreathListBacker = new List<Subject>();
return ThreathListBacker;
}
}
Then it will always exist when you need it. I like this pattern - you don't get these really big constructors (normal classes) or Awake methods ($$anonymous$$onoBehaviours) that sets up everything
There we go I was doing init with Start(), just moved relevant stuff to Awake() and its working fine! No errors.
The 2nd approach is interesting too, but I'm not as familiar with pros and cons of properties like that so I'll stay as is for the time being =)
Thanks for the help!
Your answer
Follow this Question
Related Questions
Understanding Linq and orderbydescending 1 Answer
Strange Behavior with Dictionary of List of generic Actions 0 Answers
How to add dictionary keys with defined values to a list 1 Answer
Dictionary Keys to Stack Enemy Types 1 Answer
Dictionary key exists but corresponding value pair is non-existent 0 Answers