为什么Select()不能将IEnumerable<dynamic>转换为IEnumerable<StrongType>?

6

我正在尝试使用Dapper将我的数据库表映射到C#中的类型,但是有些类型需要额外的元素,而这些元素并不在表中。为了实现这一点,我正在使用一个工厂,可以接受列值并设置适当的属性。

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

目前这会导致返回语句产生错误:

无法将表达式类型'System.Collections.Generic.IEnumerable<dynamic>'转换为返回类型'System.Collections.Generic.IEnumerable<IMyType>'

我的工厂类大致如下:

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));
  }
}

为什么Select()方法不返回IEnumerable<IMyType>?我需要做什么才能让它工作?这是错误的方法吗?是否有更好的方法?

2个回答

10

最简单的解决方法就是使用 Cast<> LINQ 运算符:

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

或者,您可以为每个元素进行强制类型转换:

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

目前它不能正常工作,因为IEnumerable<dynamic>IEnumerable<IMyType>之间没有可用的隐式转换。 IEnumerable<dynamic>可以以任意数量的方式实现,而且由于每个项都将动态生成,所以没有理由认为结果值将实现IEnumerable<IMyType>

我同意第二种形式看起来实际上并没有添加任何内容,但编译器并不检查_myTypeFactory.Create(o)的所有可能返回类型-它将整个表达式视为动态值,即表达式的类型为dynamic。因此,Select结果仍然是IEnumerable<dynamic>类型。

另一种选择是指定Select的泛型类型参数。

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

这是试图强制将lambda表达式转换为Func<dynamic, IMyType> - 我认为这样做会起作用...

编辑:如评论中所述,强制方法调用在编译时解析也可以修复此问题。基本上取决于哪种方式更易读。


@Hellfire:我很高兴你理解了,我自己也试了几次才弄明白 :) - Jon Skeet
谢谢Jon。一旦你解释了,它就变得很清晰了。类型转换确实很有效,并且对我的目的来说足够了。第三个选项不行。 - Doug Wilson
IMyType的强制转换视为红色干鱼,实际上在这种情况下您不需要动态调用MyTypeFactory.Create(dynamic o)。您只需要访问在Create内部完成的属性即可。 - jbtule
@jbtule:这并不是一个红鲱鱼 - 它只是一种替代方案。我同意,可以将调用设置为非动态的,也可以将结果转换 - 任何一种方法都可以正常工作。 - Jon Skeet
@jon-skeet,让我换一种方式来表达。Dapper使用dynamic是因为它的属性在运行时确定,具有动态行为。正是Dapper声明的dynamic导致了Create被动态调用,但在运行时没有什么新的发现,显然这不是问题的意图。将接口强制转换可以使代码工作,但如果意图不是动态行为,这是否有意义? - jbtule
@jbtule:我同意让Create被动态调用没有特别的好处 - 除非OP发现这是众多解决方案中最易读的。当然,使所有这些 less dynamic 会稍微更有效率,但我认为可读性是更重要的目标。留下动态的 Create 调用可能会允许以后有更易读的 Create 调用(更多的重载)。基本上在这里有大量的选择 :) - Jon Skeet

1
最好的解决方法可能是从选择语句中删除动态调用,然后您将获得预期的静态类型 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
}

或者

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)); 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接