你快完成了!
尽量不要把所有东西都放在一个程序中。
Google 了解 SOLID 原则并记住 S!
在您的 Form1 类中,您有以下字段:temperture 和 size。在显示表单之前,您需要为这些字段赋值。
向操作员询问文件名
要计算这些值,您需要一个文件名。您决定向操作员询问文件名。因此,让我们在您的 Form1 类中创建一个小程序来执行此操作:一个请求文件名,一个读取文件内容,一个将所有内容放在一起:
private string SelectFileName()
{
using (OpenFileDialog dlg = new OpenFileDialog())
{
// Set properties before showing the dialog, for example:
dlg.Title = "Please select a file";
dlg.CheckFileExists = true;
dlg.InitialDirectory = ...
// etc. Google: OpenFileDialog class
// show the dlg, and if user presses OK, return the filename, otherwise null
var dlgResult = dlg.ShowDialog(this);
if (dlgResult == DialogResult.Ok)
return dlg.FileName;
else
return null;
}
}
OpenFileDialog 是一次性的。请记住始终将 IDisposable 对象放在 using 语句中,这样您就可以确定它在使用后被释放,即使抛出异常也是如此。
有关属性,请参阅OpenFileDialog 和基类。
使用温度读取文件
操作员选择一个文件后,您可以阅读它。显然,文件的第一行以字符串形式包含温度的数量,下一行包含字符串格式的每个温度。
您决定使用两个单独的字段:size 和 temperature。知道每个数组都有一个属性长度来保存数组中元素的数量,因此您不需要字段size。
此外,我的建议是使用List<double> 而不是double[]。仅当您在创建数组之前就知道数组的大小并且确定永远不必更改长度时才使用数组。使用列表,您不必关心其中的元素数量,如果您添加项目,列表会自动更改大小。
private List<double> Temperatures {get; set;}
(或者,如果您愿意,可以将其设为字段:`私人列表温度;)
private List<double> ReadTemperatureFile(string fileName)
{
// TODO: decide what to do if fileName null, or if file does not exist
// return empty list? or throw ArgumentNullException and FileNotFoundException?
using (var textReader = File.OpenText(fileName))
{
// first line is the expected number of Temperatures in this file
string firstLine = textReader.ReadLine();
if (firstLine == null)
{
// There is no first line
// Todo: return empty array or throw DataNotFoundException?
}
// if here: the first line has been read
int expectedNumberOfTemperatures = Int32.Parse(firstLine);
List<double> temperatures = new List<double>(firstLine);
实际上,要创建一个列表,您不需要知道它的长度。向构造函数添加预期大小只是为了提高性能。
如果您可以随意更改温度文件的格式,请考虑删除包含元素数量的行并仅保留温度。这样就可以避免第一行说有 100 个温度,但文件只包含 50 个温度值的问题。
顺便说一句,如果您绝对确定第一行可以解析为 int,请仅使用 Int32.Parse(firstLine)。如果要正确处理无效文件格式,请考虑使用:
if (!Int32.TryParse(firstLine, out int expectedNumberOfTemperatures)
{
// TODO: handle invalid file format; return empty array?
// throw InvalidDataException?
}
List<double> temperatures = new List<double>(expectedNumberOfTemperatures);
顺便说一句:OpenText 返回的 StreamReader 是 IDisposable,所以我将它包装在 using 语句中。
继续读取文件:
string temperatureText = textReader.ReadLine();
while (temperatureText != null)
{
// a line has been read, convert to double and add to array
double temperature = Int32.Parse(temperatureText);
temperatures.Add(temperature);
temperaturText = textReader.ReadLine();
}
}
您是否看到我不在乎文件中的实际温度数?如果第一行说预期有 100 个温度,但实际上有 200 个温度,我只是将它们全部读取,并将它们添加到列表中。列表会在需要时自动增长。
当然,如果您只想读取 100 个温度,即使还有剩余温度,也可以使用计数器并在读取 expectedNumberOfTemperatures 时停止。
仅当您确定文件仅包含有效温度时才使用Int32.Parse,否则使用Int32.TryParse 并决定在读取到无效行时如何处理。
顺便说一句,如果你使用LINQ,你的程序会小很多。大多数程序员会立即知道会发生什么:
(假设您可以更改文件,使其仅包含温度。)
private List<double> ReadTemperatureFile(string fileName)
{
return System.IO.File.ReadAllLines(fileName)
.Select(line => int32.Parse(line))
.ToList();
}
在文字中:读取文本文件中带有文件名的所有行。将每个读取的行解析为双精度,并将解析后的双精度序列转换为列表。
如果您真的想返回 double[],请将终止的 ToList() 替换为 ToArray()
或者,如果第一行必须保存预期的温度数量并且您想要返回所有温度,请在转换为双精度值之前跳过第一行:
return System.IO.File.ReadAllLines(fileName)
.Skip(1)
.Select(line => int32.Parse(line))
.ToList();
计算每个双打序列的总和和平均值
为了让这个过程更可重用,我不会只为列表或数组,而是为每个双精度序列:
private void CalculateAverageTemperature(IEnumerable<double> temperatures)
{
// using LINQ make this a one-liner:
return temperatures.Average();
}
事实上,我什至都懒得为此创建一个过程。
把它们放在一起:
private void FillTemperatures()
{
string fileName = this.SelectFileName();
this.Temperatures = this.ReadTemperatureFile(fileName);
double averageTemperature = this.Temperatures.Average();
this.textBoxAverage.Text = averageTemperature.ToString(); }
}
最后是你的事件处理程序:
private void OnMenuItemCalculateAverage(object sender, ...)
{
this.FillTemperatures();
}
我将事件处理程序与实际数据处理分开。如果您决定使用按钮选择文件并计算平均值,则更改将很少
结论
因为您将代码分成更小的程序,每个程序只有一项任务,所以读者更容易理解每个程序应该做什么。对您来说,对每个过程进行单元测试要容易得多。如果需要稍作更改,例如您想要一个按钮而不是菜单项,或者使用在文本框中键入文件名,或者您希望支持 OpenFileDialog 以及带有文件名的文本框,则更改很小.
此外:始终将一次性用品包装在 using 声明中。使用List<...>而不是数组,并考虑使用LINQ来处理相似项的序列。