Introduction
Learn how to use #file
for referencing files known as context variables feature with asking a question using inline version of Copilot which also works in Copilot Chat.
Goal
Although Copilot can write all code required for the task below with several prompts, the goal is not to get hung up on writing the perfect prompt but instead to have Copilot to write the bulk of the work which below is in a for statement.
Once Copilot has, in this case nailed the solution the developer is now free to make changes like exception handling and optionally use asynchronous read operation rather than synchronous read operation.
So the goal is to teach developers how to be smart about writing/engineering A.I. prompts to write difficult code (dependent on developer experience) and leave tweaks to the code to the developer, not Copilot.
💡 Make sure to check out resources at end of the article.
This article uses information from this article while here the focus also include the goal, short and to the point prompts.
Task
A developer is provided the following file structure which needs to read the data into an array using the following class/model.
File structure
- There are empty lines
- In Visual Studio project, the file is at the root of the project
- The file property Copy to Output folder is set to copy if newer
Model
internal class Friend
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
Let's ask Copilot to read the file. In any file in the project, press ALT + / which brings up Copilot.
Type the following
read # which will bring up a list of files, either traverse the list or start typing people followed by selecting people.
Next, add a space and type into followed by # and select Friends.cs same as for people.txt.
Next, type using a new class named FileOperations.
The complete question/prompt.
Next, press ENTER or click the paper airplane.
Copilot responds with;
internal class FileOperations
{
public static List<Friend> ReadFriendsFromFile(string filePath)
{
List<Friend> friends = new List<Friend>();
string[] lines = File.ReadAllLines(filePath);
for (int i = 0; i < lines.Length; i += 4)
{
string firstName = lines[i].Trim();
string lastName = lines[i + 1].Trim();
DateTime birthDate = DateTime.Parse(lines[i + 2].Trim());
Friend friend = new Friend
{
FirstName = firstName,
LastName = lastName,
BirthDate = birthDate
};
friends.Add(friend);
}
return friends;
}
}
🛑 Perfect but we need to take into consider empty lines. This was left out intentionally to keep Copilot focused on the task.
Change
string[] lines = File.ReadAllLines(filePath);
To
string[] lines = File.ReadAllLines(filePath)
.Where(line => !string.IsNullOrEmpty(line))
.ToArray();
Now try reading the file using the following.
var friends = FileOperations.ReadFriendsFromFile("people.txt");
Refinement 1
Once satisfied with the code above, lets make some minor changes.
Since the birth date has no time, change the property BirthDate to DateOnly.
internal class Friend
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateOnly BirthDate { get; set; }
}
Next, in FileOperations.ReadFriendsFromFile change.
DateTime birthDate = DateTime.Parse(lines[i + 2].Trim());
To
DateOnly birthDate = DateOnly.FromDateTime(DateTime.Parse(lines[i + 2].Trim()));
Next rename ReadFriendsFromFile
method name to ReadFriends
as FromFile is not needed as the class name indicates we are dealing with files.
Refinement 2
Let's change the variable i to index which is easier to read.
internal class FileOperations
{
public static List<Friend> ReadFriends(string filePath)
{
List<Friend> friends = new List<Friend>();
string[] lines = File.ReadAllLines(filePath)
.Where(line => !string.IsNullOrEmpty(line))
.ToArray();
for (int index = 0; index < lines.Length; index += 4)
{
string firstName = lines[index].Trim();
string lastName = lines[index + 1].Trim();
DateOnly birthDate = DateOnly.FromDateTime(DateTime.Parse(lines[index + 2].Trim()));
Friend friend = new Friend
{
FirstName = firstName,
LastName = lastName,
BirthDate = birthDate
};
friends.Add(friend);
}
return friends;
}
}
Refinement 3
Suppose, in production the file is missing, the file exists but the data is malformed? We should add a try/catch as shown below which changed the return type from a list to a tuple of list and exception.
internal class FileOperations
{
public static (List<Friend> data, Exception exception) ReadFriends(string filePath)
{
List<Friend> friends = new List<Friend>();
try
{
string[] lines = File.ReadAllLines(filePath)
.Where(line => !string.IsNullOrEmpty(line))
.ToArray();
for (int index = 0; index < lines.Length; index += 4)
{
string firstName = lines[index].Trim();
string lastName = lines[index + 1].Trim();
DateOnly birthDate = DateOnly.FromDateTime(DateTime.Parse(lines[index + 2].Trim()));
Friend friend = new Friend
{
FirstName = firstName,
LastName = lastName,
BirthDate = birthDate
};
friends.Add(friend);
}
return (friends, null);
}
catch (Exception ex)
{
return (null, ex);
}
}
}
When making the above changes yourself note Visual Studio will lite code up in red until finished.
Next change the return type as follows.
var (friends, exception) = FileOperations.ReadFriends("people.txt");
if (exception is null)
{
// read data
}
else
{
// handle exception
}
Refinement 4
If the file has a lot of data, consider reading the file asynchronously.
internal class FileOperations
{
public static async Task<(List<Friend> data, Exception exception)> ReadFriends(string filePath)
{
List<Friend> friends = new List<Friend>();
try
{
string[] lines = (await File.ReadAllLinesAsync(filePath))
.Where(line => !string.IsNullOrWhiteSpace(line))
.ToArray();
for (int index = 0; index < lines.Length; index += 4)
{
string firstName = lines[index].Trim();
string lastName = lines[index + 1].Trim();
DateOnly birthDate = DateOnly.FromDateTime(DateTime.Parse(lines[index + 2].Trim()));
Friend friend = new Friend
{
FirstName = firstName,
LastName = lastName,
BirthDate = birthDate
};
friends.Add(friend);
}
return (friends, null);
}
catch (Exception ex)
{
return (null, ex);
}
}
}
And change the calling code to
var (friends, exception) = await FileOperations.ReadFriends("people.txt");
if (exception is null)
{
// read data
}
else
{
// handle exception
}
Resources
- GitHub Copilot Completions for Visual Studio
- Referencing a file in GitHub Copilot for Visual Studio
- GitHub Copilot in Visual Studio
- Best practices (shown with VS Code but also applies to VS2022+)