I usually use a Map<Id, SObject>
This works perfectly for updates.
Since you're inserting new records, you can use a related record ID instead of the ID of the record you are trying to insert (since it obviously wont have an Id yet).
Alternatively
If you have no related records, you can change the data type of the key to something else that maps up to the record.
The question is, what criteria are you to define a duplicate? If it is complex, you can try to create a hash of the criteria and insert that into a Map<String, SOBject>
For instance, if you are using both a date field and a user's Name, you could do something like the following:
Map<String, Bar__c> insertMap = new Map<String, Bar__c>();
for (Foo__c f : foos) {
// String hash = f.Id; // Example of using a related record Id
String hash = system.hashCode(f.Owner.Name + String.valueOf(f.createdDate)); // Example of using a generated hash
if (!insertMap.containsKey(hash)) {
// Create a new record if it doesn't exist
// before attempting to modify it
insertMap.put(
hash,
new Bar__c();
);
}
// The record should always exist here so go ahead and
// modify it
insertMap.get(hash).Field__c = 'value';
}
if (!insertMap.isEmpty()) {
insert insertMap.values();
}
Doing things this way helps ensure that you can de-dup without adding the SObject to a set which has many issues as other's have pointed out. Instead you're de-duping based on your key. This pattern has served me very well and typically requires less code then some of the other approaches mentioned.