Utilisation de l'opérateur '?.' dans foreach : se prémunir contre NullReferenceException qui ne fonctionne pas

0832_foreach_ConditionalAccess_ru / image1.png

Aimez-vous l'opĂ©rateur « ? Â» ? Qui n'aime pas ? Beaucoup de gens aiment ces chèques nuls laconiques. Cependant, aujourd'hui, nous allons parler du cas oĂą l'opĂ©rateur '?.' ne crĂ©e qu'une illusion de sĂ©curitĂ©. Il s'agit de l'utiliser dans une boucle foreach.







Je propose de commencer par un petit problème d'autotest. Jetez un Ĺ“il au code suivant :







void ForeachTest(IEnumerable<String> collection)
{
  // #1
  foreach (var item in collection.NotNullItems())
    Console.WriteLine(item);

  // #2
  foreach (var item in collection?.NotNullItems())
    Console.WriteLine(item);
}
      
      





, , collection — null? , ?. . ? .







, . , .







, . C#, "The foreach statement".







0832_foreach_ConditionalAccess_ru / image2.png







, , "expression". — — , . " ", "expression" . .







'?.' foreach



, ?.







.







var b = a?.Foo();
      
      





:







  • a == null, b == null;
  • a != null, b == a.Foo().


foreach. , .







void Foo1(IEnumerable<String> collection)
{
  foreach (var item in collection)
    Console.WriteLine(item);
}
      
      





IL , C#, foreach. :







void Foo2(IEnumerable<String> collection)
{
  var enumerator = collection.GetEnumerator();
  try
  {
    while (enumerator.MoveNext())
    {
      var item = enumerator.Current;
      Console.WriteLine(item);
    }
  }
  finally
  {
    if (enumerator != null)
    {
      enumerator.Dispose();
    }
  }
}
      
      





. foreach . , , , for. . , foreach .







— collection.GetEnumerator(). ( ) , GetEnumerator. , NullReferenceException.







, ?. foreach:







static void Foo3(Wrapper wrapper)
{
  foreach (var item in wrapper?.Strings)
    Console.WriteLine(item);
}
      
      





:







static void Foo4(Wrapper wrapper)
{
  IEnumerable<String> strings;
  if (wrapper == null)
  {
    strings = null;
  }
  else
  {
    strings = wrapper.Strings;
  }

  var enumerator = strings.GetEnumerator();
  try
  {
    while (enumerator.MoveNext())
    {
      var item = enumerator.Current;
      Console.WriteLine(item);
    }
  }
  finally
  {
    if (enumerator != null)
    {
      enumerator.Dispose();
    }
  }
}
      
      





, , GetEnumerator (strings.GetEnumerator). , strings null, wrapper — null. , , , ?. ( ). string.GetEnumerator() NullReferenceException.







?. foreach , .







?



— , . . , , foreach null. . , .







void Test1(IEnumerable<String> collection, 
          Func<String, bool> predicate)
{
  foreach (var item in collection?.Where(predicate))
    Console.WriteLine(item);
}
      
      





— .







void Test2(IEnumerable<String> collection, 
          Func<String, bool> predicate)
{
  var query = collection?.Where(predicate);
  foreach (var item in query)
    Console.WriteLine(item);
}
      
      





.







void Test3(IEnumerable<String> collection, 
          Func<String, bool> predicate,
          bool flag)
{
  var query = collection != null ? collection.Where(predicate) : null;
  foreach (var item in query)
    Console.WriteLine(item);
}
      
      





PVS-Studio: V3080 Possible null dereference. Consider inspecting 'query'.







.







IEnumerable<String> GetPotentialNull(IEnumerable<String> collection,
                                     Func<String, bool> predicate,
                                     bool flag)
{
  return collection != null ? collection.Where(predicate) : null;
}

void Test4(IEnumerable<String> collection, 
          Func<String, bool> predicate,
          bool flag)
{
  foreach (var item in GetPotentialNull(collection, predicate, flag))
    Console.WriteLine(item);
}
      
      





PVS-Studio: V3080 Possible null dereference of method return value. Consider inspecting: GetPotentialNull(...).







Test3 Test4 , Test1 Test2 — ? , :







  • , ?.;
  • , - null, .


, :







  • ;
  • ( / , / ..);
  • , .


?



2 : V3105 V3153.







. PVS-Studio 7.13, .







V3105 , ?., foreach.







void Test(IEnumerable<String> collection, 
          Func<String, bool> predicate)
{
  var query = collection?.Where(predicate);
  foreach (var item in query)
    Console.WriteLine(item);
}
      
      





PVS-Studio: V3105 The 'query' variable was used after it was assigned through null-conditional operator. NullReferenceException is possible.







V3153 , ?. foreach.







void Test(IEnumerable<String> collection, 
          Func<String, bool> predicate)
{
  foreach (var item in collection?.Where(predicate))
    Console.WriteLine(item);
}
      
      





PVS-Studio: V3153 Enumerating the result of null-conditional access operator can lead to NullReferenceException. Consider inspecting: collection?.Where(predicate).









— . , , open source . V3105 V3153 !







. , . , .







RavenDB



private void HandleInternalReplication(DatabaseRecord newRecord, 
                                       List<IDisposable> instancesToDispose)
{
  var newInternalDestinations =
        newRecord.Topology?.GetDestinations(_server.NodeTag,
                                            Database.Name,
                                            newRecord.DeletionInProgress,
                                            _clusterTopology,
                                            _server.Engine.CurrentState);
  var internalConnections 
        = DatabaseTopology.FindChanges(_internalDestinations, 
                                       newInternalDestinations);

  if (internalConnections.RemovedDestiantions.Count > 0)
  {
    var removed = internalConnections.RemovedDestiantions
                                     .Select(r => new InternalReplication
      {
        NodeTag = _clusterTopology.TryGetNodeTagByUrl(r).NodeTag,
        Url = r,
        Database = Database.Name
      });

    DropOutgoingConnections(removed, instancesToDispose);
  }
  if (internalConnections.AddedDestinations.Count > 0)
  {
    var added = internalConnections.AddedDestinations
                                   .Select(r => new InternalReplication
    {
      NodeTag = _clusterTopology.TryGetNodeTagByUrl(r).NodeTag,
      Url = r,
      Database = Database.Name
    });
    StartOutgoingConnections(added.ToList());
  }
  _internalDestinations.Clear();
  foreach (var item in newInternalDestinations)
  {
    _internalDestinations.Add(item);
  }
}
      
      





. , . , , , . ;)







, .







private void HandleInternalReplication(DatabaseRecord newRecord, 
                                       List<IDisposable> instancesToDispose)
{
  var newInternalDestinations = newRecord.Topology?.GetDestinations(....);
  ....
  foreach (var item in newInternalDestinations)
    ....
}
      
      





newInternalDestinations ?.. newRecord.Topology — null, newInternalDestinations null. foreach NullReferenceException.







PVS-Studio: V3105 The 'newInternalDestinations' variable was used after it was assigned through null-conditional operator. NullReferenceException is possible. ReplicationLoader.cs 828







, — newInternalDestinations — DatabaseTopology.FindChanges, null ( newDestinations):







internal static 
(HashSet<string> AddedDestinations, HashSet<string> RemovedDestiantions)
FindChanges(IEnumerable<ReplicationNode> oldDestinations, 
            List<ReplicationNode> newDestinations)
{
  ....
  if (newDestinations != null)
  {
    newList.AddRange(newDestinations.Select(s => s.Url));
  }
  ....
}
      
      





MSBuild



public void LogTelemetry(string eventName, 
                         IDictionary<string, string> properties)
{
  string message 
           = $"Received telemetry event '{eventName}'{Environment.NewLine}";

  foreach (string key in properties?.Keys)
  {
    message += $"  Property '{key}' = '{properties[key]}'{Environment.NewLine}";
  }
  ....
}
      
      





PVS-Studio: V3153 Enumerating the result of null-conditional access operator can lead to NullReferenceException. Consider inspecting: properties?.Keys. MockEngine.cs 159







?. foreach. , , . - , . ;)







Nethermind



.







public NLogLogger(....)
{
  ....

  foreach (FileTarget target in global::NLog.LogManager
                                            .Configuration
                                           ?.AllTargets
                                            .OfType<FileTarget>())
  {
    ....
  }
  ....
}
      
      





PVS-Studio: V3153 Enumerating the result of null-conditional access operator can lead to NullReferenceException. NLogLogger.cs 50







"" NullReferenceException ?. foreach. , Configuration null. , "".







Roslyn



private ImmutableArray<char>
GetExcludedCommitCharacters(ImmutableArray<RoslynCompletionItem> roslynItems)
{
  var hashSet = new HashSet<char>();
  foreach (var roslynItem in roslynItems)
  {
    foreach (var rule in roslynItem.Rules?.FilterCharacterRules)
    {
      if (rule.Kind == CharacterSetModificationKind.Add)
      {
        foreach (var c in rule.Characters)
        {
          hashSet.Add(c);
        }
      }
    }
  }

  return hashSet.ToImmutableArray();
}
      
      





PVS-Studio: V3153 Enumerating the result of null-conditional access operator can lead to NullReferenceException. CompletionSource.cs 482







, ? , PVS-Studio .







PVS-Studio



, - , . :)







0832_foreach_ConditionalAccess_ru / image3.png







PVS-Studio PVS-Studio. :









, V3153 V3105, , . , ?. foreach, ( ). , . , , . ;)







, :







public override void
VisitAnonymousObjectCreationExpression(
  AnonymousObjectCreationExpressionSyntax node)
{
  foreach (var initializer in node?.Initializers)
    initializer?.Expression?.Accept(this);
}
      
      





, ?. — , . , ( Crysis), — .







'?.' foreach ?



, . . , ??.







NullReferenceException:







static void Test(IEnumerable<String> collection,
                 Func<String, bool> predicate)
{
  foreach (var item in collection?.Where(predicate))
    Console.WriteLine(item);
}
      
      





:







static void Test(IEnumerable<String> collection,
                 Func<String, bool> predicate)
{
  foreach (var item in    collection?.Where(predicate) 
                       ?? Enumerable.Empty<String>())
  {
    Console.WriteLine(item);
  }
}
      
      





?. null, ?? Enumerable.Empty<String>() — , . , , , null.







static void Test(IEnumerable<String> collection,
                 Func<String, bool> predicate)
{
  if (collection != null)
  {
    foreach (var item in collection.Where(predicate))
      Console.WriteLine(item);
  }
}
      
      





, , , , .









, .







void ForeachTest(IEnumerable<String> collection)
{
  // #1
  foreach (var item in collection.NotNullItems())
    Console.WriteLine(item);

  // #2
  foreach (var item in collection?.NotNullItems())
    Console.WriteLine(item);
}
      
      





, #2 NullReferenceException. #1? , , NullReferenceException — collection.NotNullItems(). ! , NotNullItems — , :







public static IEnumerable<T>
NotNullItems<T>(this IEnumerable<T> collection) where T : class
{
  if (collection == null)
    return Enumerable.Empty<T>();

  return collection.Where(item => item != null);
}
      
      





, , collection null. Enumerable.Empty<T>(), foreach . #1 , collection — null.







, . collection — null, NotNullItems . , , collection — null. , , : GetEnumerator() .







, collection.NotNullItems() NullReferenceException, ' ' — collection?.NotNullItems() — .









:







  • ?. foreach — ;
  • .


, , .







PVS-Studio 7.13. , - ?. ? .







, , Twitter-.







, : Sergey Vasiliev. The '?.' Operator in foreach Will Not Protect From NullReferenceException.








All Articles