Структура C# IEnumerator/yield потенциально плохо?

В вашем коде много основных синтаксических ошибок. Я бы порекомендовал вам сначала изучить Java. Ниже приведен исправленный вариант вашего кода.

package com.example.mod7;

import java.text.DecimalFormat;

import com.example.mod7.R;
import android.app.Activity;
import android.os.Bundle;

import android.view.Menu;
import android.view.MenuItem;
import android.view.View.OnClickListener;

import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

public class Main extends Activity { 

    double costPerTicket=59.99;
    int numberofTickets;
    double totalCost;
    String groupChoice; 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final EditText tickets= (EditText)findViewById(R.id.txtTickets);
        final Spinner group = (Spinner)findViewById(R.id.txtGroups);
        Button cost=(Button)findViewById(R.id.btnCost);
        final TextView result=(TextView)findViewById(R.id.txtResult);


        cost.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(view arg0) {
                numberofTickets=Integer.parseInt(tickets.getText().toString());
                totalCost=costPerTicket*numberofTickets;
                DecimalFormat currency=new DecimalFormat("$###,###.##");
                groupChoice=group.getSelectedItem().toString();
                result.setText("Total cost for"+ groupChoice + "is"+ currency.format(totalCost));
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu){
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}
32
задан Jon Skeet 29 April 2009 в 19:38
поделиться

10 ответов

Это уравновешивающее действие: хотите ли вы немедленно принудительно перенести все данные в память, чтобы вы могли освободить соединение, или хотите получить выгоду от потоковой передачи данных за счет связывания до соединения все это время?

То, как я на это смотрю, это решение должно быть принято самим абонентом, который знает больше о том, что они хотят сделать. Если вы пишете код, используя блок итератора, вызывающая сторона может очень легко легко превратить эту потоковую форму в полностью буферизованную форму:

List<string> stuff = new List<string>(GetStuff(connectionString));

Если, с другой стороны, вы делаете буферизацию самостоятельно, нет способ, которым вызывающий абонент может вернуться к потоковой модели.

Поэтому я, вероятно, использовал бы потоковую модель и в документации явно сказал бы , что он делает, и посоветовал бы вызывающему абоненту принять соответствующее решение. Возможно, вы даже захотите предоставить вспомогательный метод для вызова потоковой версии и преобразования ее в список.

Конечно, если вы не доверяете своим вызывающим сторонам в принятии соответствующего решения, и у вас есть веские основания полагать, что им никогда не захочется передавать данные (например, они никогда не получат много возврата), а затем перейти к списку. В любом случае, документируйте это - это может очень хорошо повлиять на то, как используется возвращаемое значение.

Другой вариант для работы с большими объемами данных - это, конечно, использовать пакеты - это несколько отходит от исходного вопроса, но это Другой подход для рассмотрения в ситуации, когда потоковая передача обычно привлекательна.

не доверяйте своим абонентам принятие соответствующего решения, и у вас есть веские основания полагать, что они никогда не захотят потоковую передачу данных (например, в любом случае они никогда не вернут много), а затем перейдите к подходу списка. В любом случае, документируйте это - это может очень хорошо повлиять на то, как используется возвращаемое значение.

Другой вариант для работы с большими объемами данных - это, конечно, использовать пакеты - это несколько отходит от исходного вопроса, но это Другой подход для рассмотрения в ситуации, когда потоковая передача обычно привлекательна.

не доверяйте своим абонентам принятие соответствующего решения, и у вас есть веские основания полагать, что они никогда не захотят потоковую передачу данных (например, в любом случае они никогда не вернут много), а затем перейдите к подходу списка. В любом случае, документируйте это - это может очень хорошо повлиять на то, как используется возвращаемое значение.

Другой вариант для работы с большими объемами данных - это, конечно, использовать пакеты - это несколько отходит от исходного вопроса, но это Другой подход для рассмотрения в ситуации, когда потоковая передача обычно привлекательна.

44
ответ дан 27 November 2019 в 20:22
поделиться

I've bumped into this wall a few times. SQL database queries are not easily streamable like files. Instead, query only as much as you think you'll need and return it as whatever container you want (IList<>, DataTable, etc.). IEnumerable won't help you here.

0
ответ дан 27 November 2019 в 20:22
поделиться

What you can do is use a SqlDataAdapter instead and fill a DataTable. Something like this:

public IEnumerable<string> GetStuff(string connectionString)
{
    DataTable table = new DataTable();
    using (SqlConnection sqlConnection = new SqlConnection(connectionString))
    {
        string commandText = "GetStuff";
        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection))
        {
            sqlCommand.CommandType = CommandType.StoredProcedure;
            SqlDataAdapter dataAdapter = new SqlDataAdapter(sqlCommand);
            dataAdapter.Fill(table);
        }

    }
    foreach(DataRow row in table.Rows)
    {
        yield return row["myImportantColumn"].ToString();
    }
}

This way, you're querying everything in one shot, and closing the connection immediately, yet you're still lazily iterating the result. Furthermore, the caller of this method can't cast the result to a List and do something they shouldn't be doing.

-1
ответ дан 27 November 2019 в 20:22
поделиться

You're not always unsafe with the IEnumerable. If you leave the framework call GetEnumerator (which is what most of the people will do), then you're safe. Basically, you're as safe as the carefullness of the code using your method:

class Program
{
    static void Main(string[] args)
    {
        // safe
        var firstOnly = GetList().First();

        // safe
        foreach (var item in GetList())
        {
            if(item == "2")
                break;
        }

        // safe
        using (var enumerator = GetList().GetEnumerator())
        {
            for (int i = 0; i < 2; i++)
            {
                enumerator.MoveNext();
            }
        }

        // unsafe
        var enumerator2 = GetList().GetEnumerator();

        for (int i = 0; i < 2; i++)
        {
            enumerator2.MoveNext();
        }
    }

    static IEnumerable<string> GetList()
    {
        using (new Test())
        {
            yield return "1";
            yield return "2";
            yield return "3";
        }
    }

}

class Test : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("dispose called");
    }
}

Whether you can affort to leave the database connection open or not depends on your architecture as well. If the caller participates in an transaction (and your connection is auto enlisted), then the connection will be kept open by the framework anyway.

Another advantage of yield is (when using a server-side cursor), your code doesn't have to read all data (example: 1,000 items) from the database, if your consumer wants to get out of the loop earlier (example: after the 10th item). This can speed up querying data. Especially in an Oracle environment, where server-side cursors are the common way to retrieve data.

10
ответ дан 27 November 2019 в 20:22
поделиться

В дополнение - обратите внимание, что подход IEnumerable - по сути , что поставщики LINQ (LINQ-to-SQL, LINQ-to-Entities) делают для жизни. Подход имеет преимущества, как говорит Джон. Однако есть и определенные проблемы - в частности (для меня) с точки зрения (комбинации) разделения |

Я имею в виду следующее:

  • в сценарии MVC (например) вы хотите, чтобы ваш " так что вы можете проверить, работает ли он на контроллере , а не на представлении (без необходимости вызывать .ToList () и т. д.)
  • вы можете не гарантирует, что другая реализация DAL сможет иметь возможность для потоковой передачи данных (например, вызов POX / WSE / SOAP не может обычно передавать записи); и вы не обязательно хотите, чтобы поведение было смешным (например, соединение все еще открыто во время итерации с одной реализацией и закрыто для другой)

Это немного связано с моими мыслями здесь: Pragmatic LINQ

Но я должен подчеркнуть - бывают моменты, когда потоковое вещание крайне желательно. Это не простая вещь "всегда против никогда" ...

так что вы можете проверить, работает ли он на контроллере , а не на представлении (без необходимости вызывать .ToList () и т. д.)
  • вы можете не гарантирует, что другая реализация DAL сможет иметь возможность для потоковой передачи данных (например, вызов POX / WSE / SOAP не может обычно передавать записи); и вы не обязательно хотите, чтобы поведение было смешным (например, соединение все еще открыто во время итерации с одной реализацией и закрыто для другой)
  • Это немного связано с моими мыслями здесь: Pragmatic LINQ

    Но я должен подчеркнуть - бывают моменты, когда потоковое вещание крайне желательно. Это не простая вещь "всегда против никогда" ...

    не представление (без необходимости вспоминать вызов .ToList () и т. д.)
  • , вы не можете гарантировать, что другая реализация DAL будет способна потоковые данные (например, вызов POX / WSE / SOAP обычно не может передавать записи); и вы не обязательно хотите, чтобы поведение было смешным (например, соединение все еще открыто во время итерации с одной реализацией и закрыто для другой)
  • Это немного связано с моими мыслями здесь: Pragmatic LINQ

    Но я должен подчеркнуть - бывают моменты, когда потоковое вещание крайне желательно. Это не простая вещь "всегда против никогда" ...

    не представление (без необходимости вспоминать вызов .ToList () и т. д.)
  • , вы не можете гарантировать, что другая реализация DAL будет способна потоковые данные (например, вызов POX / WSE / SOAP обычно не может передавать записи); и вы не обязательно хотите, чтобы поведение было смешным (например, соединение все еще открыто во время итерации с одной реализацией и закрыто для другой)
  • Это немного связано с моими мыслями здесь: Pragmatic LINQ

    Но я должен подчеркнуть - бывают моменты, когда потоковое вещание крайне желательно. Это не простая вещь "всегда против никогда" ...

    вызов POX / WSE / SOAP обычно не может передавать записи); и вы не обязательно хотите, чтобы поведение было смешным (например, соединение все еще открыто во время итерации с одной реализацией и закрыто для другой)

    Это немного связано с моими мыслями здесь: Pragmatic LINQ

    Но я должен подчеркнуть - бывают моменты, когда потоковое вещание крайне желательно. Это не простая вещь "всегда против никогда" ...

    вызов POX / WSE / SOAP обычно не может передавать записи); и вы не обязательно хотите, чтобы поведение было смешным (например, соединение все еще открыто во время итерации с одной реализацией и закрыто для другой)

    Это немного связано с моими мыслями здесь: Pragmatic LINQ

    Но я должен подчеркнуть - бывают моменты, когда потоковое вещание крайне желательно. Это не простая вещь "всегда против никогда" ...

    6
    ответ дан 27 November 2019 в 20:22
    поделиться

    Вы ничего не упускаете. Ваш пример показывает, как НЕ использовать доходность. Добавьте элементы в список, закройте соединение и верните список. Ваша сигнатура метода все еще может возвращать IEnumerable.

    Редактировать: Тем не менее, Джон имеет точку зрения (так удивлен!): Бывают редкие случаи, когда потоковая передача на самом деле является лучшим решением с точки зрения производительности. В конце концов, если мы говорим здесь о 100 000 (1 000 000? 10 000 000?) Строк, вы не хотите загружать все это в память в первую очередь.

    8
    ответ дан 27 November 2019 в 20:22
    поделиться

    Нет, вы на правильном пути ... yield заблокирует читателя ... вы можете проверить это, выполнив другой вызов базы данных при вызове IEnumerable

    1
    ответ дан 27 November 2019 в 20:22
    поделиться

    Не используйте здесь выход. Ваш образец в порядке.

    -2
    ответ дан 27 November 2019 в 20:22
    поделиться

    Единственный способ вызвать проблемы - это если вызывающий абонент злоупотребляет протоколом IEnumerable . Правильный способ его использования - вызвать Dispose для него, когда он больше не нужен.

    Реализация, сгенерированная yield return , принимает вызов Dispose как сигнал для выполнения любых открытых блоков finally , которые в вашем примере будут вызывать Dispose для объектов, которые вы создали в , используя операторы .

    Существует ряд языковых функций (в частности, foreach ), которые позволяют очень легко использовать IEnumerable .

    1
    ответ дан 27 November 2019 в 20:22
    поделиться

    You could always use a separate thread to buffer the data (perhaps to a queue) while also doing a yeild to return the data. When the user requests data (returned via a yeild), an item is removed from the queue. Data is also being continuously added to the queue via the separate thread. That way, if the user requests the data fast enough, the queue is never very full and you do not have to worry about memory issues. If they don't, then the queue will fill up, which may not be so bad. If there is some sort of limitation you would like to impose on memory, you could enforce a maximum queue size (at which point the other thread would wait for items to be removed before adding more to the queue). Naturally, you will want to make sure you handle resources (i.e., the queue) correctly between the two threads.

    As an alternative, you could force the user to pass in a boolean to indicate whether or not the data should be buffered. If true, the data is buffered and the connection is closed as soon as possible. If false, the data is not buffered and the database connection stays open as long as the user needs it to be. Having a boolean parameter forces the user to make the choice, which ensures they know about the issue.

    0
    ответ дан 27 November 2019 в 20:22
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: