gpt4 book ai didi

c# - IEnumerable 的实现适用于foreach,但不适用于LINQ

转载 作者:行者123 更新时间:2023-11-30 15:49:02 25 4
gpt4 key购买 nike

我目前使用以下基本集合。

  abstract class BaseCollection : Collection<BaseRecord>
{
}


我想用下面的通用集合替换它。但是,由于当前实现BaseCollection的派生类的数量众多,这并不是一件容易的事。

  abstract class BaseCollection<TRecord> : Collection<TRecord> where TRecord : BaseRecord,new()
{

}


除了要进行大修以外,我想慢慢介绍这个新系列。

   abstract class BaseCollection<TRecord> : BaseCollection,IEnumerable<TRecord> where TRecord : BaseRecord,new()
{
public new IEnumerator<TRecord> GetEnumerator()
{
return Items.Cast<TRecord>().GetEnumerator();
}
}


虽然我可以使用foreach枚举集合,但是下面的LINQ语句不会编译。这可能吗?我意识到这有点骇人听闻,但我不确定该如何做。

   class Program
{
static void Main(string[] args)
{
DerivedCollection derivedCollection = new DerivedCollection
{
new DerivedRecord(),
new DerivedRecord()
};
foreach (var record in derivedCollection)
{
record.DerivedProperty = "";
}
var records = derivedCollection.Where(d => d.DerivedProperty == "");
}
}


这是上面使用的两个记录。谢谢。

   class DerivedRecord : BaseRecord
{
public string DerivedProperty { get; set; }
}

abstract class BaseRecord
{
public string BaseProperty { get; set; }
}


这是派生的集合。

   class DerivedCollection : BaseCollection<DerivedRecord>
{

}

最佳答案

Foreach使用一些“魔术”来找出要循环的类型。它不需要集合支持任何IEnumerable。这可能解释了差异。

问题在于,编译器无法找出Where的通用类型参数。如果这样做,它将起作用:

IEnumerable<DerivedRecord> ie = derivedCollection;
ie.Where(d => d.DerivedProperty == "");


没有使用强制类型转换,但是可用选项集减少到了一个。

或者,您可以显式指定type参数:

derivedCollection.Where<DerivedRecord>(d => d.DerivedProperty == "");


我认为要解决您的问题,您将必须停止 BaseCollection继承另一个版本的 IEnumerable<T>

abstract class BaseCollection
{
private readonly Collection<BaseRecord> _realCollection = new Collection<BaseRecord>();

public void Add(BaseRecord rec)
{
_realCollection.Add(rec);
}

public IEnumerable<BaseRecord> Items
{
get { return _realCollection; }
}
}


在那里,我仍在实例化 Collection<T>,但是作为单独的对象而不是基类。然后将其转发给它,以根据需要模仿其API。

派生的通用版本需要完全实现 IEnumerable<T>

class BaseCollection<TRecord> : BaseCollection, IEnumerable<TRecord> 
where TRecord : BaseRecord,new()
{
public IEnumerator<TRecord> GetEnumerator()
{
return Items.Cast<TRecord>().GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}


然后,您的其余示例代码可以正常工作,而无需我最初发布的解决方法,因为它只有一个 IEnumerable<T>实现可以使用。

更新,更多详细信息

这是相同情况的精简版本,用 IEnumerable断开了所有链接,从头开始声明了所有内容,因此我们可以看到发生了什么。

public class R1 { }
public class R2 { }
public interface I<T> { }
public class C1<T> : I<T> { }
public class C2 : C1<R1>, I<R2> { }

class Program
{
public static I<T> M<T>(I<T> i) { return i; }

static void Main(string[] args)
{
var c2 = new C2();
var v = M(c2); // Compiler error - no definition for M
}
}


R1R2类似于两个记录类。在您的版本中, R2继承了 R1-但这与此问题无关,因此我将其省略。 I<T>是通用接口,代表 IEnumerable<T>。但是我的版本没有方法!它们也与此问题无关。

然后,我将您的集合类继承系统折叠到仅两层。基类 C1实现 I<T>,然后派生类 C2选择让 C2实现 I<R1>,然后直接实现 I<R2>。层数也没有区别。也没有使用 where声明类型约束,因此这里也将其省略。

结果是 C2具有 I<T>的两种实现: I<R1>I<R2>。它从 C1继承而来,另一则添加自己。

最后,我有了方法 M,在Linq中代表 Where。它不一定是显示问题的扩展方法,因此为了清楚起见,我将其设为普通的静态方法。

因此,当我们调用方法 M时,编译器必须弄清楚 T是什么。它通过查看传递给该方法的唯一参数的方法来做到这一点,该方法必须支持 I<T>。不幸的是,我们传递了一些支持 I<R1>I<R2>的东西,那么类型推断过程如何在它们之间做出选择?不可以

由于我的界面没有方法,因此将 new放在方法前面显然不会帮助我,这就是为什么它对您没有帮助的原因。问题不是确定接口中要调用的方法,而是确定将 M的参数视为 I<R1>还是 I<R2>

编译器为什么不将其报告为类型推断问题?根据C#3.0规范,它只是不这样做-首先运行类型推断,以产生一组可用的重载,然后运行重载解析以选择最佳选择。如果类型推断不能在泛型方法的两个可能扩展之间决定,它将消除两者,因此重载解析甚至都看不到它们,因此错误消息表明没有任何适用的方法称为 M

(但是,如果您有Resharper,则它具有自己的编译器,可用于在IDE中提供更详细的错误,在这种情况下,它会明确指出:“无法从用法中推断出方法M的类型参数”。)

现在,为什么 foreach与众不同?因为它甚至都不安全!它可以追溯到添加泛型之前。它甚至不看接口。它只是在循环访问的任何类型中寻找一个名为 GetEnumerator的公共方法。例如:

public class C
{
public IEnumerator GetEnumerator() { return null; }
}


就编译器而言,这是一个非常好的集合! (当然,它会在运行时崩溃,因为它返回一个空的 IEnumerator。)但是请注意,没有 IEnumerable或任何泛型。这意味着 foreach进行隐式转换。

因此,要将其与您的代码相关联,您可以从 BaseRecordDerivedRecord进行“向下转换”,并使用Linq中的 Cast运算符实现。好吧, foreach还是为您做到了。在上面的示例中, C实际上是 object类型的项目的集合。但是我可以写:

foreach (string item in new C())
{
Console.WriteLine(item.Length);
}


编译器愉快地插入从 objectstring的静默转换。这些物品可能什么都没有......!

这就是 var出现的原因-始终使用 var声明您的 foreach循环变量,这样编译器将不会插入强制类型转换。它将使变量具有最具体的类型,可以在编译时从集合中推断出该类型。

关于c# - IEnumerable <T>的实现适用于foreach,但不适用于LINQ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2133942/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com