3

In Apex, I cannot successfully execute this anonymous block without casting on Ln 2 below:

List<SObject> lst = new List<SObject>{ new Custom_SObject__c() };

Custom_SObject__c cstom = lst[0];

It will complain with this error: Illegal assignment from SObject to Custom_SObject__c

But I can execute this without an error:

List<SObject> lst = new List<SObject>{ new Custom_SObject__c() };

List<Custom_SObject__c> cstoms = lst;

I find this inconsistent and would expect the execution to fail with an error of this sort: Illegal assignment from List<SObject> to List<Custom_SObject__c>.

Why is this so?

1
  • 5
    There are plenty of minor inconsistencies like this. You just have to live with it. (A pet example I have is if you have a Database.Batchable implementation you can declare it like public with sharing class MyBatch implements Database.Batchable<SObject> and then implement the execute method templated on a different type, like public void execute(Database.BatchableContext context, List<Account> scope), without any complaints.)
    – Phil W
    Commented Nov 26, 2020 at 16:55

1 Answer 1

5

This is some WTFApex magic related to dynamic SOQL queries.

There is another similar example where you can do some funky implicit casting with SOQL queries and Database.Query('soql');:

Foo__c foo = Database.Query('SELECT Id FROM Foo__c');
List<Foo__c> foos = Database.Query('SELECT Id FROM Foo__c');

Note how it will happily give you either a single sObject or a collection.

Lets expand your example out to see where it might break:

List<SObject> randomsObjects = new List<SObject>{new Account(), new Contact()};
List<Account> accounts = randomsObjects;
for(Account acc : accounts) {
    // Ruh-roh! That Contact isn't an Account sObject
    System.debug(acc.Name);
}

This results in the following error on the for statement:

System.QueryException: Dynamic query yields incompatible SObject type Contact for loop variable of type Account

So it appears Apex is aware of the potential problems at runtime and associates it back with Dynamic SOQL queries as being the primary source of this occurring.

This also relates to the batch example that Phil W gave.

A pet example I have is if you have a Database.Batchable implementation you can declare it like public with sharing class MyBatch implements Database.Batchable<SObject> and then implement the execute method templated on a different type, like public void execute(Database.BatchableContext context, List<Account> scope), without any complaints.

In your Database.Batchable start method it is possible to return a Database.QueryLocator that is constructed from a SOQL string. Apex can't confirm the resulting sObject type until runtime, but allows you to declare that the batch is going to execute over a fixed sObject type.

4
  • 3
    Collections are broken in a series of fun and amusing (until you're trying to use them) ways. This is a subset of that problem.
    – sfdcfox
    Commented Nov 26, 2020 at 23:31
  • 1
    Also, interesting link, although some of those WTFs are not actually WTFs, but a natural consequence of object-oriented programming that allows nulls.
    – sfdcfox
    Commented Nov 26, 2020 at 23:36
  • 1
    Having a non-nullable type notation in Apex would be super useful. A nullable Boolean is all sorts of confusing. Commented Nov 26, 2020 at 23:40
  • I would have expected List<Account> accounts = randomsObjects; to fail at runtime because I assumed Apex was implicitly casting all elements from List<sObject> to List<Account>. Normally, a cast from List<Object> to List<Something> would involve iterating over all elements and casting them individually. I think casting is deferred for efficiency's sake. No point looping over everything when the user's going to do that anyway. This happens only with sObjects. I created my own three types and it won't even allow explicit casts from List<MyObject> to List<MyConcrete> Commented Nov 30, 2020 at 5:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .