Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Select() convert a IEnumerable<dynamic> to IEnumerable<StrongType>?

Tags:

c#

linq

dapper

I'm trying to use Dapper simply to map my database tables to types in C#, however, some of my types need additional elements that are not in the table. To do this I am using a factory that can take column values and set the appropriate properties.

public IEnumerable<IMyType> All() {
  var query = _connection.Query("SELECT * FROM [table]");
  return query.Select(o => _myTypeFactory.Create(o));
}

Currently this is resulting the return statement generating an error:

Cannot convert expression type 'System.Collections.Generic.IEnumerable<dynamic>' to return type 'System.Collections.Generic.IEnumerable<IMyType>'

My factory class looks something like this:

public class MyTypeFactory {
  public IMyType Create(dynamic o) {
    return Create((String) o.Code, (Int32) o.KeyID);
  }
  public IMyType Create(String code, Int32 keyID) {
    return new MyType(code, Cache.Lookup(keyID));
  }
}

Why doesn't the Select() method return IEnumerable<IMyType>? What do I need to do to make this work? Is this just the wrong approach and there's a better way?

like image 738
Doug Wilson Avatar asked Aug 30 '11 18:08

Doug Wilson


2 Answers

The simplest fix is just to use the Cast<> LINQ operator:

public IEnumerable<IMyType> All() {
  var query = _connection.Query("SELECT * FROM [table]");
  return query.Select(o => _myTypeFactory.Create(o))
              .Cast<IMyType>();
}

Alternatively, you could cast each element:

public IEnumerable<IMyType> All() {
  var query = _connection.Query("SELECT * FROM [table]");
  return query.Select(o => (IMyType) _myTypeFactory.Create(o));
}

It doesn't currently work because there's simply no implicit conversion available between IEnumerable<dynamic> and IEnumerable<IMyType>. IEnumerable<dynamic> could be implemented in any number of ways, and given that each item will be generated dynamically there's no reason to suppose the result value will implement IEnumerable<IMyType>.

I agree that it looks like the second form isn't actually adding anything, but the compiler doesn't check all the possible return types of _myTypeFactory.Create(o) - it treats that whole expression as a dynamic value, i.e. the expression is of type dynamic. Therefore the Select result is still of type IEnumerable<dynamic>.

Another option is to specify the generic type argument to Select.

public IEnumerable<IMyType> All() {
  var query = _connection.Query("SELECT * FROM [table]");
  return query.Select<IMyType>(o => _myTypeFactory.Create(o));
}

That's attempting to force the lambda expression to a Func<dynamic, IMyType> - I believe that will work...

EDIT: As noted in comments, forcing the method invocation to be resolved at compile-time will fix it too. Basically it depends what you find most readable.

like image 178
Jon Skeet Avatar answered Sep 17 '22 22:09

Jon Skeet


The best fix is probably to remove the dynamic invocation from the select statement then you'll get your expected static type IEnumerable<IMyType>.

public IEnumerable<IMyType> All() {
  var query = _connection.Query("SELECT * FROM [table]");
  return query.Select(o => _myTypeFactory.Create((Object)o)); //cast dynamic type to Object
}

OR

public IEnumerable<IMyType> All() {
      IEnumerable<object> query = _connection.Query("SELECT * FROM [table]"); //IEnumerable<dynamic> is the same as IEnumerable<object>
      return query.Select(o => _myTypeFactory.Create(o)); 
}
like image 27
jbtule Avatar answered Sep 20 '22 22:09

jbtule