C# - 无法弄清楚如何将自定义对象读取到文件、添加新的自定义对象以及将新集写入覆盖的文件
C# - Can't figure out how to read custom objects to file, add new custom objects, and write new set to overwritten file
我知道这可能令人困惑,所以让我解释一下。我正在尝试制作一个模仿日历程序的程序,它几乎可以工作。我只是难以写入输出文件。我有一个可以写入文件的自定义对象 Event
,我可以从文件中读取字符串并根据该信息创建新对象。但是,我在尝试向旧的 Events
添加新的 Events
然后将所有这些信息写回文件时遇到了困难,同时还覆盖了文件的最新版本。我在我的程序中加入了评论,希望这能让它更清晰一些。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Globalization;
using System.IO;
// Date: 4/24/17
// Purpose (according to Reddit spec)
/* create a program that will allow you to enter events organizable by hour. There must be menu options of some form, and you must be
* able to easily edit, add, and delete events without directly changing the source code.
* (note that by menu i dont necessarily mean gui. as long as you can easily access the different options and receive prompts and
* instructions telling you how to use the program, it will probably be fine) */
namespace ChallengeOneIntermediate
{
class Event
{
string eventName;
DateTime eventDay;
DateTime eventTime;
public string EventName
{
get { return eventName; }
set { eventName = value; }
}
public DateTime EventDay
{
get { return eventDay; }
set { eventDay = value; }
}
public DateTime EventTime
{
get { return eventTime; }
set { eventTime = value; }
}
public Event(string name, DateTime day, DateTime time)
{
eventName = name;
eventDay = day;
eventTime = time;
}
}
class Program
{
public static List<string> ADD_CHOICES = new List<string> { "1", "1.", "add", "add event", "1 add event", "1. add event",
"1.add event", "1addevent", "1add event", "1.add event",
"1 addevent", "1. addevent", "1add", "1.add", "1 add", "1. add" };
public static List<string> DEL_CHOICES = new List<string> { "2", "2.", "delete", "delete event", "2 delete event",
"2. delete event", "2deleteevent", "2.deleteevent", "2delete event",
"2.delete event", "2 deleteevent", "2. deleteevent", "2delete",
"2.delete", "2 delete", "2. delete" };
public static List<string> EDIT_CHOICES = new List<string> { "3", "3.", "edit", "edit event", "3 edit event", "3. edit event",
"3editevent", "3.editevent", "3edit event", "3.edit event",
"3 editevent", "3. editevent", "3edit", "3.edit", "3 edit",
"3. edit" };
public static List<string> VIEW_CHOICES = new List<string> { "4", "4.", "view", "calendar", "viewcalendar", "view calendar",
"4.view", "4. view", "4.calendar", "4. calendar",
"4.viewcalendar", "4.view calendar", "4. viewcalendar",
"4. view calendar", "4viewcalendar", "4view calendar",
"4 viewcalendar", "4 view calendar" };
public static List<string> EXIT_CHOICES = new List<string> { "5", "5.", "exit", "5 exit", "5. exit", "5exit", "5.exit" };
public static List<Event> EVENT_CALENDAR = new List<Event>();
public static string PATH = @"G:\Daily Programmer\C#\Intermediate\Challenge #1\calendar.txt";
static void Main(string[] args)
{
int menuChoice;
while (true)
{
menuChoice = Menu();
if (menuChoice == 1)
AddEvent();
else if (menuChoice == 2)
DeleteEvent();
else if (menuChoice == 3)
EditEvent();
else if (menuChoice == 4)
ViewCalendar();
else if (menuChoice == 5)
break;
else
Console.WriteLine("Sorry, that's not a valid choice.\n");
}
// No point in writing to the file if there's nothing in EVENT_CALENDAR to write
if (EVENT_CALENDAR.Count > 0)
WriteToFile();
Console.ReadLine();
}
static void AddEvent()
{
DateTime newDate, newTime;
string eventName, eventDate, eventTime;
Console.Write("Enter the name of your event, or 'exit' to go back to the menu: ");
eventName = Console.ReadLine();
if (EXIT_CHOICES.Contains(eventName, StringComparer.OrdinalIgnoreCase))
return;
Console.Write("Enter the date of your event in the format MM/DD/YYYY: ");
eventDate = Console.ReadLine();
Console.Write("Enter the time of your event in the format HH:MM AM/PM: ");
eventTime = Console.ReadLine();
try
{
DateTime.TryParse(eventDate, out newDate);
DateTime.TryParse(eventTime, out newTime);
Event newCalendarEvent = new Event(eventName, newDate, newTime);
EVENT_CALENDAR.Add(newCalendarEvent);
// Uses LINQ to sort by day, then by time
EVENT_CALENDAR = EVENT_CALENDAR.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
Console.WriteLine("Event: " + eventName + " added successfully!");
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
Console.WriteLine("Sorry, you didn't enter the date and time in the right format. Please try again.");
}
}
static void DeleteEvent()
{
string userInput;
int num = 1, eventToDelete;
// Easiest way I could think of in a non-visual format to identify which event the user is trying to delete
foreach (Event eachEvent in EVENT_CALENDAR)
{
Console.WriteLine(num + ". " + eachEvent.EventName + ", {0:MM/dd/yyyy} @ {1:hh:mm tt}",
eachEvent.EventDay, eachEvent.EventTime);
num++;
}
Console.WriteLine();
Console.Write("Enter the number of the event you want to delete, or 'exit' to go back to the menu: ");
userInput = Console.ReadLine();
if (EXIT_CHOICES.Contains(userInput, StringComparer.OrdinalIgnoreCase))
return;
else
{
Int32.TryParse(userInput, out eventToDelete);
if (eventToDelete != 0 && (eventToDelete < 1 || eventToDelete > EVENT_CALENDAR.Count))
while (eventToDelete < 1 || eventToDelete > EVENT_CALENDAR.Count)
{
Console.WriteLine("Sorry, invalid choice. Please enter another number: ");
eventToDelete = Convert.ToInt32(Console.ReadLine());
}
}
EVENT_CALENDAR.RemoveAt(eventToDelete - 1);
// No need to re-sort EVENT_CALENDAR because order is maintained
Console.WriteLine("Event deleted successfully!");
}
static void EditEvent()
{
DateTime newDate, newTime;
string eventName, eventDate, eventTime, userInput;
int num = 1, eventToEdit;
// Including the sort here too because for some reason it wasn't appropriately sorting events added with blank names
// Uses LINQ to sort by day, then by time
EVENT_CALENDAR = EVENT_CALENDAR.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
foreach (Event eachEvent in EVENT_CALENDAR)
{
Console.WriteLine(num + ". " + eachEvent.EventName + ", {0:MM/dd/yyyy} @ {1:hh:mm tt}",
eachEvent.EventDay, eachEvent.EventTime);
num++;
}
Console.WriteLine();
Console.Write("Enter the number of the event you'd like to edit, or 'exit' to return to the menu: ");
userInput = Console.ReadLine();
if (EXIT_CHOICES.Contains(userInput, StringComparer.OrdinalIgnoreCase))
return;
else
{
Int32.TryParse(userInput, out eventToEdit);
if (eventToEdit != 0 && (eventToEdit < 1 || eventToEdit > EVENT_CALENDAR.Count))
while (eventToEdit < 1 || eventToEdit > EVENT_CALENDAR.Count)
{
Console.WriteLine("Sorry, invalid choice. Please enter another number: ");
eventToEdit = Convert.ToInt32(Console.ReadLine());
}
}
Console.Write("What is the new name of the event? ");
eventName = Console.ReadLine();
Console.Write("Enter the new date of your event in the format MM/DD/YYYY: ");
eventDate = Console.ReadLine();
Console.Write("Enter the new time of your event in the format HH:MM AM/PM: ");
eventTime = Console.ReadLine();
try
{
// eventToEdit acts as index + 1 here, so I have to subtract 1 to get the right index internally
EVENT_CALENDAR[eventToEdit - 1].EventName = eventName;
DateTime.TryParse(eventDate, out newDate);
EVENT_CALENDAR[eventToEdit - 1].EventDay = newDate;
DateTime.TryParse(eventTime, out newTime);
EVENT_CALENDAR[eventToEdit - 1].EventTime = newTime;
// Uses LINQ to order by day, then by time
EVENT_CALENDAR = EVENT_CALENDAR.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
Console.WriteLine("Event: " + eventName + " edited successfully!");
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
Console.WriteLine("Sorry, you didn't enter the date and time in the right format. Please try again.");
}
}
static int IntMenuChoice(string stringMenuChoice)
{
if (ADD_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 1;
else if (DEL_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 2;
else if (EDIT_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 3;
else if (VIEW_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 4;
else if (EXIT_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 5;
else
return -1;
}
static int Menu()
{
string strChoice;
int intChoice;
Console.WriteLine();
Console.WriteLine("-- Options --\n");
Console.WriteLine("1. Add event\n");
Console.WriteLine("2. Delete event\n");
Console.WriteLine("3. Edit event\n");
Console.WriteLine("4. View calendar\n");
Console.WriteLine("5. Exit\n");
Console.Write("Choice: ");
strChoice = Console.ReadLine();
Console.WriteLine();
intChoice = IntMenuChoice(strChoice);
return intChoice;
}
//
/* The purpose of this function is to try and merge old sessions of a user's calendar with a new session.
* It's unlikely the user has this program running all the time, and what's the point of having a calendar that only stores
* events on a session-to-session basis?
* What I'm attempting to do here is read events from the existing file, create a new list of Event objects based on
* the fields stored in each line by splitting on spaces, and then assign that sorted list to EVENT_CALENDAR.
* It seems to work locally (I tested by running a foreach() loop on EVENT_CALENDAR), but not in WriteToFile(). */
static void SortExistingFile()
{
List<Event> existingDates = new List<Event>();
DateTime existDate, existTime;
foreach(string fileLine in File.ReadLines(PATH))
{
// Split() has to split on characters, not strings, so single quotes
string[] fields = fileLine.Split(' ');
DateTime.TryParse(fields[1], out existDate);
DateTime.TryParse(fields[2], out existTime);
existingDates.Add(new Event(fields[0], existDate, existTime));
}
// Orders all the events read from the file
existingDates = existingDates.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
/* I decided to clear the old calendar and indivudally re-write events to it because I wasn't sure
* if trying to do assignment (ex. EVENT_CALENDAR = existingDates) would only work locally and not
* maintain its state after the function exits */
EVENT_CALENDAR.Clear();
foreach(Event oldEvent in existingDates)
EVENT_CALENDAR.Add(oldEvent);
}
static void ViewCalendar()
{
int num = 1;
if (EVENT_CALENDAR.Count == 0)
{
Console.WriteLine("There's nothing in the calendar!");
return;
}
foreach (Event eachEvent in EVENT_CALENDAR)
{
Console.WriteLine(num + ". " + eachEvent.EventName + ", {0:MM/dd/yyyy} @ {1:hh:mm tt}",
eachEvent.EventDay, eachEvent.EventTime);
num++;
}
}
static void WriteToFile()
{
// I know that this probably hurts performance because then the file's being written to 1 + 2 + 3 + ... + n times
// I wasn't sure of another way I could write Event objects to a file in human-readable format
try
{
foreach(Event eachEvent in EVENT_CALENDAR)
{
if (File.Exists(PATH))
{
// I append all the new events to the existing events in the file
File.AppendAllText(PATH, String.Format("{0} {1:MM/dd/yyyy} {2:hh:mm tt}" + Environment.NewLine, eachEvent.EventName,
eachEvent.EventDay, eachEvent.EventTime));
}
else
{
FileStream myFile = File.Create(PATH);
myFile.Close();
File.WriteAllText(PATH, String.Format("{0} {1:MM/dd/yyyy} {2:hh:mm tt}" + Environment.NewLine, eachEvent.EventName,
eachEvent.EventDay, eachEvent.EventTime));
}
}
}
catch (ArgumentNullException ane)
{
Console.WriteLine("ArgumentNullException\n");
Console.WriteLine(ane.Message);
}
catch (IOException ioe)
{
Console.WriteLine("IOException\n");
Console.WriteLine(ioe.Message);
}
catch (ArgumentException ae)
{
Console.WriteLine("ArgumentException\n");
Console.WriteLine(ae.Message);
}
catch (Exception e)
{
Console.WriteLine("Exception\n");
Console.WriteLine(e.Message);
}
// Here's where I sort all the existing events that merged with the new events and store them in EVENT_CALENDAR
SortExistingFile();
string allEvents = String.Empty;
// This is what I can't get to work
/* My intent here was to store everything in one massive string.
* Why do this? I want to overwrite the file each time the program closes so all events are in order.
* I couldn't get File.Copy(PATH, PATH, true) to work.
* WriteAllText() does what I want, but it wouldn't work in a foreach() loop because it would always just overwrite
* the previous event, so only the most recent event in the calendar would be stored. */
foreach(Event eachEvent in EVENT_CALENDAR)
{
allEvents += eachEvent.EventName + " " + String.Format("{0:MM/dd/yyyy}", eachEvent.EventDay.ToString()) +
" " + String.Format("{0:hh:mm tt", eachEvent.EventTime.ToString()) + Environment.NewLine;
}
// If the above code worked, then I could do this and all the events would be stored in the calendar file.
File.WriteAllText(PATH, allEvents);
}
}
}
Clarity Edit: 我无法让副本工作,因为我在尝试写入已经打开的文件时抛出错误(我相信这是因为我试图从 PATH 复制到 PATH)。我想到了用换行符将所有内容写入一个字符串,然后将其存储在一个文件中的想法。但是,我收到一个错误,指出 DateTime 对象无效,尽管它在用户输入它时有效并且我将其转换为 DateTime 对象。 WriteAllText 工作正常。我只是无法在 foreach 循环中使用它,因为在循环的每次迭代中调用 WriteAllText 会覆盖当前会话中写入的最后一个事件。
我知道这不是专门作为控制台应用程序制作的理想程序,但它几乎完全按预期工作,除了从文件读取并覆盖回同一文件的这一方面。这是来自 DailyProgrammer 的挑战,我决定通过添加将日历写入文件的功能来稍微增强它。
NullReferenceException 问题,但为什么呢?
感谢 JohnG,我能够重写 SortExistingFile()
函数以正确读取,并重写我的 WriteToFile()
函数以正确覆盖。但是,现在我在 SortExistingFile()
中的 eventFieldArray
上收到 NullReferenceException,即使我已经尝试使用不同的值对其进行初始化。我不知道是什么原因造成的。这是修改后的功能:
static void SortExistingFile()
{
List<Event> existingDates = new List<Event>();
DateTime existDateTime;
string readLine;
string[] eventFieldArray = null;
long fileLength = new FileInfo(PATH).Length;
// Check if file has any contents so there's no attempt to read the file when it doesn't contain anything
if (fileLength == 0)
return;
else
{
try
{
using (StreamReader sr = new StreamReader(PATH))
{
while (fileLength > 0)
{
readLine = sr.ReadLine();
eventFieldArray = readLine.Split(',');
DateTime.TryParse(eventFieldArray[1], out existDateTime);
existingDates.Add(new Event(eventFieldArray[0], existDateTime));
fileLength--;
}
}
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
Console.ReadLine();
}
}
EVENT_CALENDAR = existingDates.OrderBy(x => x.EventDateTime).ToList();
}
我红色了一点代码,我觉得这个问题有点混乱。我在您的代码中找不到从文本文件加载某种数据模型的位置。因此,我首先想到的是你的做法是错误的。
首先,你应该知道这样的程序应该由数据库支持(甚至 SQLite),这将使你能够有效地读取事件,文本文件不是理想的方法这种情况,尤其是if/when会有很多事件。
如果您想继续使用文本文件,我的建议如下:
- 为您的日历事件(例如:
CalEvent
)创建一个新的 class,然后可以在 List<CalEvent>
中使用它来保存您的整个日历。
- 将
List<CalEvent>
序列化为 Json 文本文件,使用类似 Json.NET 的方式将其存储在驱动器上。
- 反序列化 Json 文本文件并取回
List<CalEvent>
。
然后,例如,您可以使用 LINQ 在 List<CalEvent>
中搜索事件,如下所示:
List<CalEvent> calEvents = readJsonFileOutput();
DateTime now = DateTime.Now;
DateTime twodays = DateTime.Now.AddDays(2);
IEnumerable<CalEvent> eventsInTheNextTwoDays = calEvents.Where(e => e.DateAndTime >= now && e.DateAndTime <= twodays).ToList();
如果您绝对需要在文本文件中以排序的方式存储这些事件,您可以使用 calEvents.OrderBy(e => e.DateAndTime)
。显然所有这些代码只是一个例子。
我看到的问题是您从未读入您保存的数据。我知道您想通过将这些数据写入文件来保存这些数据……但除非您将其读回,否则它是无用的。
在你的例子中,当程序启动时......它应该去寻找文件calander.txt
,如果找到,读入现有事件以允许用户view/edit/delete
这些已经存在的事件。当您读入这些事件时,您可以将它们按原样放入一个列表中,并且您可以像往常一样从该列表中添加、编辑和删除事件。当用户添加、删除或更新事件时,您在列表中更新此事件,然后您只需将整个列表写入文件并覆盖以前的版本。如果您从未读回数据,那么一开始就没有理由保存它。
如果您将整个 "calendar.txt" 文件读入一个事件对象列表,那么对列表所做的添加、更改或删除将包含更新的数据,所以...鉴于此,每次添加一个事件、删除或更改,您只需将更新事件对象列表写入文件。不需要检查它是否存在,如果不存在则创建一个新文件,如果存在则直接覆盖它。然后当你稍后再次 运行 程序时,它会读入之前的更改。这似乎是你想要做的。
下面是一个简单的写入方法,它采用事件对象列表和字符串文件名将列表写入文件。写入此文件后,您应该更改代码以在程序启动时将此文件读取到事件对象列表中。我希望这是有道理的。
下面的代码使用了 StreamWriter
,应该也能正常工作。我所做的另一个更改是 Event
class 有两个 DataTime
对象……一个用于日期,另一个用于时间……这是不必要的,因为一个 DateTime
对象可以并且应该包含事件的日期和时间。最后的变化是代码似乎使用空格“”作为文件中事件名称和事件日期时间的分隔符。我将其更改为有一个逗号“,”分隔符,这样您就可以使用带空格的事件名称。希望这有帮助。
private void WriteToFile(List<Event> allEvents, string filepath) {
try {
using (StreamWriter sw = new StreamWriter(filepath)) {
foreach (Event curEvent in allEvents) {
sw.WriteLine(curEvent.EventName + "," + curEvent.EventDateTime.ToString());
}
}
}
catch (Exception e) {
Console.WriteLine("Write Error: " + e.Message);
Console.ReadKey();
}
}
编辑:使用 IComparable 接口排序...
查看您的排序方法……可能有一种更简单的方法来对列表进行排序,方法是为 Events
class 实现 IComparable<Event>
接口,如下所示。 CompareTo
将根据事件名称对两个 Event
对象进行排序,如果事件名称相同,则为事件日期。
要实现这个...将接口放在 Event
签名中,如下所示:
public class Event : IComparable<Event> {
之后,你添加IComparable<Event>
接口...编译器会报错CompareTo
下面的方法没有实现。添加 CompareTo
方法来解决这个问题。
public int CompareTo(Event other) {
int result = this.eventName.CompareTo(other.eventName);
if (result == 0) {
return this.eventDateTime.CompareTo(other.eventDateTime);
} else {
return result;
}
}
之后,您可以对事件对象列表进行排序,如下所示:
EVENT_CALENDAR.Sort();
上面的调用将使用 Event
class 中的 CompareTo
方法对列表进行排序。恕我直言,比 SortExistingFile()
方法容易得多。
我知道这可能令人困惑,所以让我解释一下。我正在尝试制作一个模仿日历程序的程序,它几乎可以工作。我只是难以写入输出文件。我有一个可以写入文件的自定义对象 Event
,我可以从文件中读取字符串并根据该信息创建新对象。但是,我在尝试向旧的 Events
添加新的 Events
然后将所有这些信息写回文件时遇到了困难,同时还覆盖了文件的最新版本。我在我的程序中加入了评论,希望这能让它更清晰一些。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Globalization;
using System.IO;
// Date: 4/24/17
// Purpose (according to Reddit spec)
/* create a program that will allow you to enter events organizable by hour. There must be menu options of some form, and you must be
* able to easily edit, add, and delete events without directly changing the source code.
* (note that by menu i dont necessarily mean gui. as long as you can easily access the different options and receive prompts and
* instructions telling you how to use the program, it will probably be fine) */
namespace ChallengeOneIntermediate
{
class Event
{
string eventName;
DateTime eventDay;
DateTime eventTime;
public string EventName
{
get { return eventName; }
set { eventName = value; }
}
public DateTime EventDay
{
get { return eventDay; }
set { eventDay = value; }
}
public DateTime EventTime
{
get { return eventTime; }
set { eventTime = value; }
}
public Event(string name, DateTime day, DateTime time)
{
eventName = name;
eventDay = day;
eventTime = time;
}
}
class Program
{
public static List<string> ADD_CHOICES = new List<string> { "1", "1.", "add", "add event", "1 add event", "1. add event",
"1.add event", "1addevent", "1add event", "1.add event",
"1 addevent", "1. addevent", "1add", "1.add", "1 add", "1. add" };
public static List<string> DEL_CHOICES = new List<string> { "2", "2.", "delete", "delete event", "2 delete event",
"2. delete event", "2deleteevent", "2.deleteevent", "2delete event",
"2.delete event", "2 deleteevent", "2. deleteevent", "2delete",
"2.delete", "2 delete", "2. delete" };
public static List<string> EDIT_CHOICES = new List<string> { "3", "3.", "edit", "edit event", "3 edit event", "3. edit event",
"3editevent", "3.editevent", "3edit event", "3.edit event",
"3 editevent", "3. editevent", "3edit", "3.edit", "3 edit",
"3. edit" };
public static List<string> VIEW_CHOICES = new List<string> { "4", "4.", "view", "calendar", "viewcalendar", "view calendar",
"4.view", "4. view", "4.calendar", "4. calendar",
"4.viewcalendar", "4.view calendar", "4. viewcalendar",
"4. view calendar", "4viewcalendar", "4view calendar",
"4 viewcalendar", "4 view calendar" };
public static List<string> EXIT_CHOICES = new List<string> { "5", "5.", "exit", "5 exit", "5. exit", "5exit", "5.exit" };
public static List<Event> EVENT_CALENDAR = new List<Event>();
public static string PATH = @"G:\Daily Programmer\C#\Intermediate\Challenge #1\calendar.txt";
static void Main(string[] args)
{
int menuChoice;
while (true)
{
menuChoice = Menu();
if (menuChoice == 1)
AddEvent();
else if (menuChoice == 2)
DeleteEvent();
else if (menuChoice == 3)
EditEvent();
else if (menuChoice == 4)
ViewCalendar();
else if (menuChoice == 5)
break;
else
Console.WriteLine("Sorry, that's not a valid choice.\n");
}
// No point in writing to the file if there's nothing in EVENT_CALENDAR to write
if (EVENT_CALENDAR.Count > 0)
WriteToFile();
Console.ReadLine();
}
static void AddEvent()
{
DateTime newDate, newTime;
string eventName, eventDate, eventTime;
Console.Write("Enter the name of your event, or 'exit' to go back to the menu: ");
eventName = Console.ReadLine();
if (EXIT_CHOICES.Contains(eventName, StringComparer.OrdinalIgnoreCase))
return;
Console.Write("Enter the date of your event in the format MM/DD/YYYY: ");
eventDate = Console.ReadLine();
Console.Write("Enter the time of your event in the format HH:MM AM/PM: ");
eventTime = Console.ReadLine();
try
{
DateTime.TryParse(eventDate, out newDate);
DateTime.TryParse(eventTime, out newTime);
Event newCalendarEvent = new Event(eventName, newDate, newTime);
EVENT_CALENDAR.Add(newCalendarEvent);
// Uses LINQ to sort by day, then by time
EVENT_CALENDAR = EVENT_CALENDAR.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
Console.WriteLine("Event: " + eventName + " added successfully!");
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
Console.WriteLine("Sorry, you didn't enter the date and time in the right format. Please try again.");
}
}
static void DeleteEvent()
{
string userInput;
int num = 1, eventToDelete;
// Easiest way I could think of in a non-visual format to identify which event the user is trying to delete
foreach (Event eachEvent in EVENT_CALENDAR)
{
Console.WriteLine(num + ". " + eachEvent.EventName + ", {0:MM/dd/yyyy} @ {1:hh:mm tt}",
eachEvent.EventDay, eachEvent.EventTime);
num++;
}
Console.WriteLine();
Console.Write("Enter the number of the event you want to delete, or 'exit' to go back to the menu: ");
userInput = Console.ReadLine();
if (EXIT_CHOICES.Contains(userInput, StringComparer.OrdinalIgnoreCase))
return;
else
{
Int32.TryParse(userInput, out eventToDelete);
if (eventToDelete != 0 && (eventToDelete < 1 || eventToDelete > EVENT_CALENDAR.Count))
while (eventToDelete < 1 || eventToDelete > EVENT_CALENDAR.Count)
{
Console.WriteLine("Sorry, invalid choice. Please enter another number: ");
eventToDelete = Convert.ToInt32(Console.ReadLine());
}
}
EVENT_CALENDAR.RemoveAt(eventToDelete - 1);
// No need to re-sort EVENT_CALENDAR because order is maintained
Console.WriteLine("Event deleted successfully!");
}
static void EditEvent()
{
DateTime newDate, newTime;
string eventName, eventDate, eventTime, userInput;
int num = 1, eventToEdit;
// Including the sort here too because for some reason it wasn't appropriately sorting events added with blank names
// Uses LINQ to sort by day, then by time
EVENT_CALENDAR = EVENT_CALENDAR.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
foreach (Event eachEvent in EVENT_CALENDAR)
{
Console.WriteLine(num + ". " + eachEvent.EventName + ", {0:MM/dd/yyyy} @ {1:hh:mm tt}",
eachEvent.EventDay, eachEvent.EventTime);
num++;
}
Console.WriteLine();
Console.Write("Enter the number of the event you'd like to edit, or 'exit' to return to the menu: ");
userInput = Console.ReadLine();
if (EXIT_CHOICES.Contains(userInput, StringComparer.OrdinalIgnoreCase))
return;
else
{
Int32.TryParse(userInput, out eventToEdit);
if (eventToEdit != 0 && (eventToEdit < 1 || eventToEdit > EVENT_CALENDAR.Count))
while (eventToEdit < 1 || eventToEdit > EVENT_CALENDAR.Count)
{
Console.WriteLine("Sorry, invalid choice. Please enter another number: ");
eventToEdit = Convert.ToInt32(Console.ReadLine());
}
}
Console.Write("What is the new name of the event? ");
eventName = Console.ReadLine();
Console.Write("Enter the new date of your event in the format MM/DD/YYYY: ");
eventDate = Console.ReadLine();
Console.Write("Enter the new time of your event in the format HH:MM AM/PM: ");
eventTime = Console.ReadLine();
try
{
// eventToEdit acts as index + 1 here, so I have to subtract 1 to get the right index internally
EVENT_CALENDAR[eventToEdit - 1].EventName = eventName;
DateTime.TryParse(eventDate, out newDate);
EVENT_CALENDAR[eventToEdit - 1].EventDay = newDate;
DateTime.TryParse(eventTime, out newTime);
EVENT_CALENDAR[eventToEdit - 1].EventTime = newTime;
// Uses LINQ to order by day, then by time
EVENT_CALENDAR = EVENT_CALENDAR.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
Console.WriteLine("Event: " + eventName + " edited successfully!");
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
Console.WriteLine("Sorry, you didn't enter the date and time in the right format. Please try again.");
}
}
static int IntMenuChoice(string stringMenuChoice)
{
if (ADD_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 1;
else if (DEL_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 2;
else if (EDIT_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 3;
else if (VIEW_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 4;
else if (EXIT_CHOICES.Contains(stringMenuChoice, StringComparer.OrdinalIgnoreCase))
return 5;
else
return -1;
}
static int Menu()
{
string strChoice;
int intChoice;
Console.WriteLine();
Console.WriteLine("-- Options --\n");
Console.WriteLine("1. Add event\n");
Console.WriteLine("2. Delete event\n");
Console.WriteLine("3. Edit event\n");
Console.WriteLine("4. View calendar\n");
Console.WriteLine("5. Exit\n");
Console.Write("Choice: ");
strChoice = Console.ReadLine();
Console.WriteLine();
intChoice = IntMenuChoice(strChoice);
return intChoice;
}
//
/* The purpose of this function is to try and merge old sessions of a user's calendar with a new session.
* It's unlikely the user has this program running all the time, and what's the point of having a calendar that only stores
* events on a session-to-session basis?
* What I'm attempting to do here is read events from the existing file, create a new list of Event objects based on
* the fields stored in each line by splitting on spaces, and then assign that sorted list to EVENT_CALENDAR.
* It seems to work locally (I tested by running a foreach() loop on EVENT_CALENDAR), but not in WriteToFile(). */
static void SortExistingFile()
{
List<Event> existingDates = new List<Event>();
DateTime existDate, existTime;
foreach(string fileLine in File.ReadLines(PATH))
{
// Split() has to split on characters, not strings, so single quotes
string[] fields = fileLine.Split(' ');
DateTime.TryParse(fields[1], out existDate);
DateTime.TryParse(fields[2], out existTime);
existingDates.Add(new Event(fields[0], existDate, existTime));
}
// Orders all the events read from the file
existingDates = existingDates.OrderBy(x => x.EventDay).ThenBy(x => x.EventTime).ToList();
/* I decided to clear the old calendar and indivudally re-write events to it because I wasn't sure
* if trying to do assignment (ex. EVENT_CALENDAR = existingDates) would only work locally and not
* maintain its state after the function exits */
EVENT_CALENDAR.Clear();
foreach(Event oldEvent in existingDates)
EVENT_CALENDAR.Add(oldEvent);
}
static void ViewCalendar()
{
int num = 1;
if (EVENT_CALENDAR.Count == 0)
{
Console.WriteLine("There's nothing in the calendar!");
return;
}
foreach (Event eachEvent in EVENT_CALENDAR)
{
Console.WriteLine(num + ". " + eachEvent.EventName + ", {0:MM/dd/yyyy} @ {1:hh:mm tt}",
eachEvent.EventDay, eachEvent.EventTime);
num++;
}
}
static void WriteToFile()
{
// I know that this probably hurts performance because then the file's being written to 1 + 2 + 3 + ... + n times
// I wasn't sure of another way I could write Event objects to a file in human-readable format
try
{
foreach(Event eachEvent in EVENT_CALENDAR)
{
if (File.Exists(PATH))
{
// I append all the new events to the existing events in the file
File.AppendAllText(PATH, String.Format("{0} {1:MM/dd/yyyy} {2:hh:mm tt}" + Environment.NewLine, eachEvent.EventName,
eachEvent.EventDay, eachEvent.EventTime));
}
else
{
FileStream myFile = File.Create(PATH);
myFile.Close();
File.WriteAllText(PATH, String.Format("{0} {1:MM/dd/yyyy} {2:hh:mm tt}" + Environment.NewLine, eachEvent.EventName,
eachEvent.EventDay, eachEvent.EventTime));
}
}
}
catch (ArgumentNullException ane)
{
Console.WriteLine("ArgumentNullException\n");
Console.WriteLine(ane.Message);
}
catch (IOException ioe)
{
Console.WriteLine("IOException\n");
Console.WriteLine(ioe.Message);
}
catch (ArgumentException ae)
{
Console.WriteLine("ArgumentException\n");
Console.WriteLine(ae.Message);
}
catch (Exception e)
{
Console.WriteLine("Exception\n");
Console.WriteLine(e.Message);
}
// Here's where I sort all the existing events that merged with the new events and store them in EVENT_CALENDAR
SortExistingFile();
string allEvents = String.Empty;
// This is what I can't get to work
/* My intent here was to store everything in one massive string.
* Why do this? I want to overwrite the file each time the program closes so all events are in order.
* I couldn't get File.Copy(PATH, PATH, true) to work.
* WriteAllText() does what I want, but it wouldn't work in a foreach() loop because it would always just overwrite
* the previous event, so only the most recent event in the calendar would be stored. */
foreach(Event eachEvent in EVENT_CALENDAR)
{
allEvents += eachEvent.EventName + " " + String.Format("{0:MM/dd/yyyy}", eachEvent.EventDay.ToString()) +
" " + String.Format("{0:hh:mm tt", eachEvent.EventTime.ToString()) + Environment.NewLine;
}
// If the above code worked, then I could do this and all the events would be stored in the calendar file.
File.WriteAllText(PATH, allEvents);
}
}
}
Clarity Edit: 我无法让副本工作,因为我在尝试写入已经打开的文件时抛出错误(我相信这是因为我试图从 PATH 复制到 PATH)。我想到了用换行符将所有内容写入一个字符串,然后将其存储在一个文件中的想法。但是,我收到一个错误,指出 DateTime 对象无效,尽管它在用户输入它时有效并且我将其转换为 DateTime 对象。 WriteAllText 工作正常。我只是无法在 foreach 循环中使用它,因为在循环的每次迭代中调用 WriteAllText 会覆盖当前会话中写入的最后一个事件。
我知道这不是专门作为控制台应用程序制作的理想程序,但它几乎完全按预期工作,除了从文件读取并覆盖回同一文件的这一方面。这是来自 DailyProgrammer 的挑战,我决定通过添加将日历写入文件的功能来稍微增强它。
NullReferenceException 问题,但为什么呢?
感谢 JohnG,我能够重写 SortExistingFile()
函数以正确读取,并重写我的 WriteToFile()
函数以正确覆盖。但是,现在我在 SortExistingFile()
中的 eventFieldArray
上收到 NullReferenceException,即使我已经尝试使用不同的值对其进行初始化。我不知道是什么原因造成的。这是修改后的功能:
static void SortExistingFile()
{
List<Event> existingDates = new List<Event>();
DateTime existDateTime;
string readLine;
string[] eventFieldArray = null;
long fileLength = new FileInfo(PATH).Length;
// Check if file has any contents so there's no attempt to read the file when it doesn't contain anything
if (fileLength == 0)
return;
else
{
try
{
using (StreamReader sr = new StreamReader(PATH))
{
while (fileLength > 0)
{
readLine = sr.ReadLine();
eventFieldArray = readLine.Split(',');
DateTime.TryParse(eventFieldArray[1], out existDateTime);
existingDates.Add(new Event(eventFieldArray[0], existDateTime));
fileLength--;
}
}
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
Console.ReadLine();
}
}
EVENT_CALENDAR = existingDates.OrderBy(x => x.EventDateTime).ToList();
}
我红色了一点代码,我觉得这个问题有点混乱。我在您的代码中找不到从文本文件加载某种数据模型的位置。因此,我首先想到的是你的做法是错误的。
首先,你应该知道这样的程序应该由数据库支持(甚至 SQLite),这将使你能够有效地读取事件,文本文件不是理想的方法这种情况,尤其是if/when会有很多事件。
如果您想继续使用文本文件,我的建议如下:
- 为您的日历事件(例如:
CalEvent
)创建一个新的 class,然后可以在List<CalEvent>
中使用它来保存您的整个日历。 - 将
List<CalEvent>
序列化为 Json 文本文件,使用类似 Json.NET 的方式将其存储在驱动器上。 - 反序列化 Json 文本文件并取回
List<CalEvent>
。
然后,例如,您可以使用 LINQ 在 List<CalEvent>
中搜索事件,如下所示:
List<CalEvent> calEvents = readJsonFileOutput();
DateTime now = DateTime.Now;
DateTime twodays = DateTime.Now.AddDays(2);
IEnumerable<CalEvent> eventsInTheNextTwoDays = calEvents.Where(e => e.DateAndTime >= now && e.DateAndTime <= twodays).ToList();
如果您绝对需要在文本文件中以排序的方式存储这些事件,您可以使用 calEvents.OrderBy(e => e.DateAndTime)
。显然所有这些代码只是一个例子。
我看到的问题是您从未读入您保存的数据。我知道您想通过将这些数据写入文件来保存这些数据……但除非您将其读回,否则它是无用的。
在你的例子中,当程序启动时......它应该去寻找文件calander.txt
,如果找到,读入现有事件以允许用户view/edit/delete
这些已经存在的事件。当您读入这些事件时,您可以将它们按原样放入一个列表中,并且您可以像往常一样从该列表中添加、编辑和删除事件。当用户添加、删除或更新事件时,您在列表中更新此事件,然后您只需将整个列表写入文件并覆盖以前的版本。如果您从未读回数据,那么一开始就没有理由保存它。
如果您将整个 "calendar.txt" 文件读入一个事件对象列表,那么对列表所做的添加、更改或删除将包含更新的数据,所以...鉴于此,每次添加一个事件、删除或更改,您只需将更新事件对象列表写入文件。不需要检查它是否存在,如果不存在则创建一个新文件,如果存在则直接覆盖它。然后当你稍后再次 运行 程序时,它会读入之前的更改。这似乎是你想要做的。
下面是一个简单的写入方法,它采用事件对象列表和字符串文件名将列表写入文件。写入此文件后,您应该更改代码以在程序启动时将此文件读取到事件对象列表中。我希望这是有道理的。
下面的代码使用了 StreamWriter
,应该也能正常工作。我所做的另一个更改是 Event
class 有两个 DataTime
对象……一个用于日期,另一个用于时间……这是不必要的,因为一个 DateTime
对象可以并且应该包含事件的日期和时间。最后的变化是代码似乎使用空格“”作为文件中事件名称和事件日期时间的分隔符。我将其更改为有一个逗号“,”分隔符,这样您就可以使用带空格的事件名称。希望这有帮助。
private void WriteToFile(List<Event> allEvents, string filepath) {
try {
using (StreamWriter sw = new StreamWriter(filepath)) {
foreach (Event curEvent in allEvents) {
sw.WriteLine(curEvent.EventName + "," + curEvent.EventDateTime.ToString());
}
}
}
catch (Exception e) {
Console.WriteLine("Write Error: " + e.Message);
Console.ReadKey();
}
}
编辑:使用 IComparable 接口排序...
查看您的排序方法……可能有一种更简单的方法来对列表进行排序,方法是为 Events
class 实现 IComparable<Event>
接口,如下所示。 CompareTo
将根据事件名称对两个 Event
对象进行排序,如果事件名称相同,则为事件日期。
要实现这个...将接口放在 Event
签名中,如下所示:
public class Event : IComparable<Event> {
之后,你添加IComparable<Event>
接口...编译器会报错CompareTo
下面的方法没有实现。添加 CompareTo
方法来解决这个问题。
public int CompareTo(Event other) {
int result = this.eventName.CompareTo(other.eventName);
if (result == 0) {
return this.eventDateTime.CompareTo(other.eventDateTime);
} else {
return result;
}
}
之后,您可以对事件对象列表进行排序,如下所示:
EVENT_CALENDAR.Sort();
上面的调用将使用 Event
class 中的 CompareTo
方法对列表进行排序。恕我直言,比 SortExistingFile()
方法容易得多。