Search code examples
c#genericsfilterpredicatebson

Is there a way to create a method which could return distinct types, either Predicate<BsonDocument> or FilterDefinition<BsonDocument>?


I have the following code:

     IMongoCollection<BsonDocument> collection = _database.GetWriteCollection(typeName);
     collection.DeleteMany(x => includedIdentifiers.Contains((string)x["_id"]));

     List<BsonDocument> allRecords = await collection.Find(x => true)
         .ToListAsync();
     allRecords.RemoveAll(x => includedIdentifiers.Contains((string)x["_id"]));

I'd like to reduce the duplication and make this a little more expressive like:

     IMongoCollection<BsonDocument> collection = _database.GetWriteCollection(typeName);
     collection.DeleteMany(IsContained(includedIdentifiers));

     List<BsonDocument> allRecords = await collection.Find(x => true)
         .ToListAsync();
     allRecords.RemoveAll(IsContained(includedIdentifiers));

This won't actually compile if IsContained is defined as follows:

        private Predicate<BsonDocument> IsContained(HashSet<string> includedIdentifiers)
        {
            return x => includedIdentifiers.Contains((string)x["_id"]);
        }

The error is

Severity Code Description Project File Line Suppression State Error CS1503 Argument 1: cannot convert from 'System.Predicate<MongoDB.Bson.BsonDocument>' to 'MongoDB.Driver.FilterDefinition<MongoDB.Bson.BsonDocument>' SalesforceCache C:\projects\Salesforce Cache and Sync\SalesforceCache\Salesforce\SalesforcSyncWorker.cs 488 Active

Is there a way (perhaps adding some type parameters) which I can make one function fill both needs?


Solution

  • In C#, is there a way to create a method which could return either a class or another class?

    Short answer to the title of the question: no, but....

    A method cannot return a unique type or else another type, which have nothing in common, which are completely distinct.

    We can return only one integral numeric value like an int, or one value type instance like a struct, or one reference value like a class, or so a tuple, and also an interface.

    Why methods return just one kind of parameter in normal conditions?

    Unless we have a base class or an interface common to these types.

    For example if Predicate<> or FilterDefinition<> implements IMyInterface, we can return one or the other object as this interface:

    IMyInterface MyMethod()
    {
      if ( ... )
        return (IMyInterface)new Predicate<BsonDocument>();
      else 
        return (IMyInterface)new FilterDefinition<BsonDocument>();
    }
    

    So if Predicate<> or FilterDefinition<> have a non-generic ancestor to upcast the object to return, or implements a non-generic interface to return it, it will work.

    Non-generic because we don't have true generic polumorphism via the diamond operator available in C# yet.

    I just see that these generic types seems to come from MongoDB, and if it is the case, they do not have something in common according to this documentation.

    Here is a quick idea, a hack, perhaps not very clean, or not: return a tuple (or a struct or a class) having first item of type A and second item of type B.

    One is null and the other not null.

    So the caller can check that and use what is provided.

    For example:

    (Predicate<BsonDocument> predicate, FilterDefinition<BsonDocument> filterdefinition) MyMethod()
    {
      if ( ... )
        return (new Predicate<BsonDocument>(), null);
      else 
        return (null, FilterDefinition<BsonDocument>());
    }
    

    Used like that:

    var result = MyMethod();
    
    if ( result.predicate != null && result.filterdefinition != null )
      throw new SystemException("Two instances provided: only one is expected.");
    else
    if (result.predicate != null)
    {
      ...
    }
    else
    if (result.filterdefinition != null)
    {
      ...
    }
    else
      throw new SystemException("No instance provided: one is expected.");
    

    Throwing exceptions here is only very scrupulous and may be omitted, or not...