如何在不带循环的情况下读取可枚举而不丢失其范围

本文关键字:枚举 范围 情况下 循环 读取 | 更新日期: 2023-09-27 18:33:31

我面临着一个非常奇怪的结构。在 IEnumerable 中返回的 Foo 类型会在枚举结束后立即丢失其数据。这意味着我无法进行枚举。First(),因为数据会立即丢失。

它的循环是有效的,但因为我知道它只会包含一个奇怪的元素。

int Test(out int something)
    IEnumerable<Foo> enumeration = ...
    for (var foo in enumeration) {
        something = foo.GetSomething ();
        return foo.GetAnInt ();
    }
    something = 42;
    return 0;
}

我遇到的另一种方式是滥用Linq Select,但这同样可怕。

有没有办法解决这个限制?解决根本原因显然是优越的,但在这种情况下很难。

编辑:这是一个从事务SQL数据读取器yield returned IEnumerable<IDataRecord>

public IEnumerable<IDataRecord> ExecuteReader (SqlCommand cmd)
{
    using (var con = GetConnection()) {
        con.Open ();
        using (var tr = con.BeginTransaction ()) {
            cmd.Connection = con;
            var reader = cmd.ExecuteReader ();
            while (reader.Read ()) {
                yield return reader;
            }
            tr.Commit ();
        }
    }
}

如何在不带循环的情况下读取可枚举而不丢失其范围

问题是您的ExecuteReader方法确实只是返回SqlDataReader本身(实现IDataRecord),而不是返回数据块。因此,当您执行此操作时:

var list = ExecuteReader(...).ToList();

在这种情况下,列表的所有元素都将是同一个SqlDataReader实例,但在执行ToList后,读取器已关闭。我有点惊讶你没有得到ObjectDisposedException.

为此,您需要返回IDataRecord中的数据副本。您认为可以迭代数据记录中的元素。另一种选择是将ExecuteReader更改为以下内容:

public IEnumerable<T> ExecuteReader<T>(SqlCommand cmd, 
    Func<IDataRecord, T> recordCreator)
{
    using (var con = GetConnection()) {
        con.Open ();
        using (var tr = con.BeginTransaction()) {
            cmd.Connection = con;
            var reader = cmd.ExecuteReader();
            while (reader.Read()) {
                yield return recordCreator(reader);
            }
            tr.Commit();
        }
    }
}

这样,您可以执行以下操作:

var list = ExecuteReader(command, record => new
{
    Item1 = record.GetInt("id"),
    Item2 = record.GetString("name")
});

注意:无论如何,我不确定为什么您需要为此进行交易。

怎么样

int Test(out int something)
{
    IEnumerable<Foo> enumeration = ...
    var values = enumeration
        .Select(foo => new
                {
                    something = foo.GetSomething(),
                    anInt = foo.GetAnInt()
                })
        .FirstOrDefault();
    if (values != null)
    {
        something = values.something;
        return values.anInt;
    }
    else
    {
        something = 42;
        return 0;
    }
}

在枚举中调用GetSomethingGetAnInt

另一个想法可能是将方法的结果类型从IEnumerable转换为IEnumerator。这样,范围控制就容易多了,返回单个结果也不需要任何(假)循环。

编辑:我想我找到了重构整个问题的方法。这通过使用包含以前在方法中找到的逻辑的新一次性类来规避初始问题。它非常易读,甚至代码更少。

public TransactedConnection GetConnection (string text)
{
    return new TransactedConnection (_ConnectionString, text);
}
public class TransactedConnection : IDisposable
{
    private readonly SQLiteCommand _Cmd;
    private readonly SQLiteConnection _Con;
    private readonly SQLiteTransaction _Tr;
    public TransactedConnection (string connection, string text)
    {
        _Cmd = new SQLiteCommand (text);
        _Con = new SQLiteConnection (connection);
        _Con.Open ();
        _Cmd.Connection = _Con;
        _Tr = _Con.BeginTransaction ();
    }
    public void Dispose ()
    {
        _Tr.Commit ();
        _Tr.Dispose ();
        _Con.Dispose ();
        _Cmd.Dispose ();
    }
    public SQLiteParameterCollection Parameters
    {
        get
        {
            return _Cmd.Parameters;
        }
    }
    public int ExecuteNonQuery ()
    {
        return _Cmd.ExecuteNonQuery ();
    }
    public object ExecuteScalar ()
    {
        return _Cmd.ExecuteScalar ();
    }
    public SQLiteDataReader ExecuteReader ()
    {
        return _Cmd.ExecuteReader ();
    }
}
public void Test (string match)
{
    var text = "SELECT * FROM Data WHERE foo=@bar;";
    using (var cmd = GetConnection (text)) {
        cmd.Parameters.Add ("@bar", DbType.String).Value = match;
        using (var reader = cmd.ExecuteReader ()) {
            while (reader.Read ()) {
                Console.WriteLine (reader["foo"]);
            }
        }
    }
}