Repository: PowerShell/PowerShell
File: src/System.Management.Automation/engine/runtime/Binding/Binders.cs (C#)

Code before:
                    // As discussed with Bruce, the Where/ForEach operators should work on $null and return an empty collection.
                    // e.g. $null.Where{"I didn't run"} should return an empty collection
                    DynamicMetaObject emptyEnumerator = new DynamicMetaObject(
                        Expression.Call(Expression.NewArrayInit(typeof(object)), CachedReflectionInfo.IEnumerable_GetEnumerator),
                        BindingRestrictions.GetInstanceRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression), null))
                        .WriteToDebugLog(this);
                    BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));

                    if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase)
                    || string.Equals(Name, "PSWhere", StringComparison.OrdinalIgnoreCase))
                    {
                        return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
                    }

                    if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase)
                    || string.Equals(Name, "PSForEach", StringComparison.OrdinalIgnoreCase))
                    {
                        // We need to pass the empty enumerator to the ForEach operator, so that it can return an empty collection.
                        // The ForEach operator will not be able to call the script block if the enumerator is empty.
                    {
                        return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
                    }
                }

                return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "InvokeMethodOnNull", ParserStrings.InvokeMethodOnNull).WriteToDebugLog(this);
            }

            PSMemberInfo memberInfo;
            if (_getMemberBinder.HasInstanceMember && PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
            {
                // If there is an instance member, we generate (roughly) the following:
                //     PSMethodInfo methodInfo;
                //     if (PSInvokeMemberBinder.TryGetInstanceMethod(target.Value, Name, out methodInfoInfo))
                //         return methodInfo.Invoke(target, args);
                //     else
                //         update the site
                // We use a generic method like this because:
                //   * If one object has an instance property with a given name, it's like many others do as well
                //   * We want to avoid generating new sites for every object with an instance member
                // As an alternative, would could generate the following psuedo-code:
                //     if (target.Value == previousInstance)
                //         return optimized value (depending on the exact PSMemberInfo subclass)
                //     else update the site
                // But the assumption here is that many sites probably performs worse than the dictionary lookup
                // and unoptimized virtual call to PSMemberInfo.Value.
                //
                // The binding restrictions could avoid a version check because it's never wrong to look for an instance member,
                // but we add the check because the DLR requires a non-empty check when the target implements IDynamicMetaObjectProvider,
                // which PSObject does.  The version check is also marginally useful if we knew we'd never see another
                // instance member with this member name, but we're not tracking things to make that a useful test.


Review comment (suggestion):
```suggestion
                    if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase)
                        || string.Equals(Name, "PSForEach", StringComparison.OrdinalIgnoreCase))
                    {
                        // We need to pass the empty enumerator to the ForEach operator, so that it can return an empty collection.
                        // The ForEach operator will not be able to call the script block if the enumerator is empty.
```

Code after:
            {
                return result.UpdateComRestrictionsForPsObject(args).WriteToDebugLog(this);
            }

            var targetValue = PSObject.Base(target.Value);
            if (targetValue == null)
            {
                if (!_static && !_nonEnumerating)
                {
                    // As discussed with Bruce, the Where/ForEach operators should work on $null and return an empty collection.
                    // e.g. $null.Where{"I didn't run"} should return an empty collection
                    DynamicMetaObject emptyEnumerator = new DynamicMetaObject(
                        Expression.Call(Expression.NewArrayInit(typeof(object)), CachedReflectionInfo.IEnumerable_GetEnumerator),
                        BindingRestrictions.GetInstanceRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression), null))
                        .WriteToDebugLog(this);
                    BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));

                    // We need to pass the empty enumerator to the ForEach/Where operators, so that they can return an empty collection.
                    // The ForEach/Where operators will not be able to call the script block if the enumerator is empty.
                    if (s_whereSearchValues.Contains(Name))
                    {
                        return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
                    }

                    if (s_foreachSearchValues.Contains(Name))
                    {
                        return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
                    }
                }

                return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "InvokeMethodOnNull", ParserStrings.InvokeMethodOnNull).WriteToDebugLog(this);
            }

            PSMemberInfo memberInfo;
            if (_getMemberBinder.HasInstanceMember && PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
            {
                // If there is an instance member, we generate (roughly) the following:
                //     PSMethodInfo methodInfo;
                //     if (PSInvokeMemberBinder.TryGetInstanceMethod(target.Value, Name, out methodInfoInfo))
                //         return methodInfo.Invoke(target, args);
                //     else
                //         update the site
                // We use a generic method like this because:
                //   * If one object has an instance property with a given name, it's like many others do as well
                //   * We want to avoid generating new sites for every object with an instance member
                // As an alternative, would could generate the following psuedo-code:
                //     if (target.Value == previousInstance)
                //         return optimized value (depending on the exact PSMemberInfo subclass)
                //     else update the site
                // But the assumption here is that many sites probably performs worse than the dictionary lookup
                // and unoptimized virtual call to PSMemberInfo.Value.