Introduction
Learn how to use GitHub Copilot in Microsoft Visual Studio 2022 with real world usage from documenting code, improving code, explain code and more. Also, will talk about some features of Copilot in Microsoft Visual Studio Code.
Ways to use GitHub Copilot
- Write pull request summaries (Copilot Enterprise feature only)
- Generate commit messages
- Fix code inline
- Generate documentation for your code
- Meaningful names matter (VS Code F2)
- Create unit test
- Assist with debugging code
- Explaining code
- Documenting code
The following table was copied from this page in raw format then asked Copilot to create a three column markdown table 💚
Command | Description | Usage |
---|---|---|
/explain | Get code explanations | Open file with code or highlight code you want explained and type: /explain what is the fetchPrediction method? |
/fix | Receive a proposed fix for the problems in the selected code | Highlight problematic code and type: /fix propose a fix for the problems in fetchAirports route |
/tests | Generate unit tests for selected code | Open file with code or highlight code you want tests for and type: /tests |
/help | Get help on using Copilot Chat | Type: /help what can you do? |
/clear | Clear current conversation | Type: /clear |
/doc | Add a documentation comment | Highlight code and type: /doc |
/generate | Generate code to answer your question | Type: /generate code that validates a phone number |
/optimize | Analyze and improve running time of the selected code | Highlight code and type: /optimize fetchPrediction method |
/new | Scaffold code for a new workspace | Type: /new create a new django app |
/simplify | Simplify the selected code | Highlight code and type: /simplify |
/feedback | Provide feedback to the team | Type: /feedback |
Documenting code
Developers here tend not to document code which in some cases means that other developers may not understand the purpose of a class or method. Even when a method name is meaningful the method should be documented for several reasons, first, when a code base will have help generated from XML documentation and for clarification of usage and meaning of parameters and return types are a few reasons to document code.
Example 1
Here is a method which is easy to understand.
public static class DateTimeExtensions
{ public static DateOnly FirstDateOfWeek(this DateTime sender, DayOfWeek startOfWeek = DayOfWeek.Sunday) => DateOnly.FromDateTime(sender.AddDays(-1 * (7 + (sender.DayOfWeek - startOfWeek)) % 7));
}
When using the above method this is what is shown with Intellisense.
Using /doc feature of GitHub Copilot
public static class DateTimeExtensions
{ /// <summary> /// Calculates the first date of the week based on a given start day of the week. /// </summary> /// <param name="sender">The DateTime object representing the current date.</param> /// <param name="startOfWeek">The start day of the week (default is Sunday).</param> /// <returns>The first date of the week.</returns> public static DateOnly FirstDateOfWeek(this DateTime sender, DayOfWeek startOfWeek = DayOfWeek.Sunday) => DateOnly.FromDateTime(sender.AddDays(-1 * (7 + (sender.DayOfWeek - startOfWeek)) % 7));
}
Much better with proper documentation from Copilot.
Example 2
A developer comes across the following method has been asked to document the method.
public partial class Helpers
{ public static string NextValue(string sender, int incrementBy = 1) { string value = NumbersPattern().Match(sender).Value; return sender[..^value.Length] + (long.Parse(value) + incrementBy) .ToString().PadLeft(value.Length, '0'); } [GeneratedRegex("[0-9]+$")] private static partial Regex NumbersPattern();
}
To document the code.
- Select the method
- Press ALT + /
- Type / which provides options, select doc
- Press ENTER
Results
/// <summary>
/// Generates the next value based on the given sender string and incrementBy value.
/// </summary>
/// <param name="sender">The sender string.</param>
/// <param name="incrementBy">The value to increment by (default is 1).</param>
/// <returns>The next value.</returns>
public static string NextValue(string sender, int incrementBy = 1)
{ string value = NumbersPattern().Match(sender).Value; return sender[..^value.Length] + (long.Parse(value) + incrementBy) .ToString().PadLeft(value.Length, '0');
}
Next, jumping ahead to explaining code which will be covered later. A developer is curious how the above code works.
string value = NumbersPattern().Match(sender).Value; return sender[..^value.Length] + (long.Parse(value) + incrementBy) .ToString().PadLeft(value.Length, '0');
- Select the following body of code.
- Press ALT + /
- Press /, select explain
- Press ENTER
The following is provided and at the bottom additional help.
The selected code is a part of the NextValue method in the Helpers class. Let’s break it down step by step:
- string value = NumbersPattern().Match(sender).Value;
• This line retrieves the numeric value from the sender string using a regular expression pattern defined in the NumbersPattern method.
• The NumbersPattern method is annotated with the [GeneratedRegex(“[0-9]+$”)] attribute, which indicates that it generates a regular expression pattern that matches one or more digits at the end of a string.
• The Match method is called on the result of NumbersPattern() to find the first occurrence of the pattern in the sender string.
• The Value property of the Match result is assigned to the value variable. - return sender[..^value.Length] + (long.Parse(value) + incrementBy).ToString().PadLeft(value.Length, ‘0’);
- This line constructs and returns the next value based on the sender string and the incrementBy value.
- The sender[..^value.Length] expression is used to remove the numeric value from the end of the sender string. It uses the range operator .. to specify a range from the beginning of the string to the length of the sender string minus the length of the value string.
- (long.Parse(value) + incrementBy) parses the value string as a long integer and adds the incrementBy value to it.
- .ToString().PadLeft(value.Length, ‘0’) converts the sum to a string and pads it with leading zeros to match the length of the original value string.
- The resulting string is concatenated with the modified sender string and returned as the next value.
In summary, the selected code extracts a numeric value from the sender string using a regular expression pattern, and then constructs the next value by incrementing the extracted value by the specified incrementBy value and padding it with leading zeros.
With both example code is document and in the last code has been broken down to understand code and in some cases assist if code needs modifications.
Using Copilot to explain code
Above an example for C# was presented for explaining code, Copilot can also explain JavaScript.
A developer wants to understand the following code is for a button click event but does not understand the code and would like to understand the code.
document.getElementById('getButton').addEventListener('click', function () { const xhr = new XMLHttpRequest(); xhr.open("GET", "https://jsonplaceholder.typicode.com/users"); xhr.send(); xhr.responseType = "json"; xhr.onload = () => { if (xhr.readyState === 4 && xhr.status === 200) { const data = xhr.response; document.getElementById("people-template").innerHTML = ""; console.table(data, [ "id", "name", "email" ]); $('#people-template').append(` ${data.reduce((updated, user) => updated.concat(`<li>${user.id} <strong>Name</strong> ${user.name.padEnd(35, '.')} ${user.email}</li>`), '')}`); } else { console.log(`Error: ${xhr.status}`); } };
});
Using /explain
the following is provided.
The selected code is an event listener that triggers when the “Get” button is clicked. It performs an AJAX GET request to retrieve data from the “https://jsonplaceholder.typicode.com/users” URL. Let’s break down the code step by step:
- document.getElementById(‘getButton’).addEventListener(‘click’, function () { … });: This line adds an event listener to the “Get” button. When the button is clicked, the function inside the event listener will be executed.
- const xhr = new XMLHttpRequest();: This line creates a new XMLHttpRequest object. The XMLHttpRequest object is used to interact with servers and make HTTP requests.
- xhr.open(“GET”, “https://jsonplaceholder.typicode.com/users”);: This line initializes the request by specifying the HTTP method (GET) and the URL from which to retrieve data.
- xhr.send();: This line sends the HTTP request to the server.
- xhr.responseType = “json”;: This line sets the expected response type to JSON. This tells the browser to automatically parse the response as JSON.
- xhr.onload = () => { … };: This line sets up a callback function to handle the response when it is received. The function will be executed when the request is complete and the response is available.
- if (xhr.readyState === 4 && xhr.status === 200) { … }: This line checks if the request is complete (readyState 4) and if the response status is 200 (indicating a successful request).
- const data = xhr.response;: This line assigns the response data to the data variable.
- document.getElementById(“people-template”).innerHTML = “”;: This line clears the existing content of the “people-template” element.
- console.table(data, [“id”, “name”, “email”]);: This line logs the data in a table format to the console, displaying only the “id”, “name”, and “email” properties.
- $(‘#people-template’).append( … );: This line uses jQuery to append HTML content to the “people-template” element. It uses the data.reduce() method to iterate over the data array and generate a list item (
- ) for each user. The user’s ID, name, and email are displayed in the list item.
- console.log(Error: ${xhr.status});: If the request is not successful (status code other than 200), this line logs an error message to the console, including the status code.
In summary, when the “Get” button is clicked, the code sends an AJAX GET request to retrieve user data from a remote server. If the request is successful, the data is displayed in a table format and appended to the “people-template” element as a list. If there is an error, an error message is logged to the console.
What about CSS?
A great many developers in the world do not have a grasp of working with CSS. With that in mind a developer wants to better understand the following rule.
legend { background: #0f506f; border: solid 1px black; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; padding: 6px; color: white;
}
Use /explain
and unlike the prior examples done in Visual Studio 2022, this one is done in VS Code.
Results from Copilot
What about SQL?
A developer comes across the following and wants to understand the statement.
WITH CTE
AS (SELECT t2.id, t2.TransDate AS [Date], t2.Credit, t2.Debit, SUM(COALESCE(t1.credit, 0) - COALESCE(t1.debit, 0)) AS Balance FROM dbo.Transactions t1 INNER JOIN dbo.Transactions t2 ON t1.TransDate <= t2.TransDate GROUP BY t2.TransDate, t2.Credit, t2.Debit, t2.id)
SELECT id, [Date], Credit, Debit
FROM CTE
ORDER BY CTE.[Date];
- Select the statment
- Press ALT + /
- ENTER
/explain
- Press ENTER
Copilot provides a detailed explanation along with a suggestion to modify the statement yet in this case is guessing and the guess is wrong but may open an idea up to a developer.
What about documenting the statement.
- Select the statment
- Press ALT + /
- ENTER
/doc
- Press ENTER
/* The following query uses a common table expression (CTE) to calculate the balance of transactions over time. It retrieves the transaction ID, date, credit amount, and debit amount from the Transactions table. The CTE calculates the balance by summing the difference between the credit and debit amounts for each transaction. The result is ordered by date in ascending order.
*/ WITH CTE
AS (SELECT t2.id, t2.TransDate AS [Date], t2.Credit, t2.Debit, SUM(COALESCE(t1.credit, 0) - COALESCE(t1.debit, 0)) AS Balance FROM dbo.Transactions t1 INNER JOIN dbo.Transactions t2 ON t1.TransDate <= t2.TransDate GROUP BY t2.TransDate, t2.Credit, t2.Debit, t2.id)
SELECT id, [Date], Credit, Debit
FROM CTE
ORDER BY CTE.[Date];
Ask Copilot to refactor your code
To start off, general refactoring can be changing a method with a statement body to an expression body member using Visual Studio ReSharper extension.
Using statement body
public string[] Items()
{ return new[] { ApplicationName, ApplicationVersion, ApplicationKey };
}
Refactor to expression body
public string[] Items() => new[] { ApplicationName, ApplicationVersion, ApplicationKey };
Improve/refactor code with Copilot
Given the following method used for decades to return a list of primary keys from a database table a developer learns about Dapper and wants to rewrite this code using Dapper.
public List<int> CategoryIdentifiers()
{ var idList = new List<int>(); var selectStatement = "SELECT DISTINCT CategoryID FROM dbo.Categories "; using (var cn = new SqlConnection() { ConnectionString = ConnectionString }) { using (var cmd = new SqlCommand() { Connection = cn }) { cmd.CommandText = selectStatement; cn.Open(); var reader = cmd.ExecuteReader(); while (reader.Read()) { idList.Add(reader.GetInt32(0)); } } } return idList;
}
- Select the above method
- Press ALT + /
- Enter
Refactor this method to use Dapper.
- ENTER
The following is produced.
public List<int> CategoryIdentifiers()
{ var idList = new List<int>(); var selectStatement = "SELECT DISTINCT CategoryID FROM dbo.Categories"; using (var cn = new SqlConnection() { ConnectionString = ConnectionString }) { cn.Open(); idList = cn.Query<int>(selectStatement).AsList(); } return idList;
}
Copilot did a great job on refactoring and works for inexperienced developers but an experienced developer can farther modify the code.
Here the experienced developer understands that Dapper will open the connection for us and the SQL statement does not really need to be formatted but imagine a large complex SQL statement with many joins, this would make a difference.
public List<int> CategoryIdentifiers()
{ using SqlConnection cn = new() { ConnectionString = ConnectionString }; return cn.Query<int>("""
SELECT DISTINCT CategoryID FROM dbo.Categories """).AsList();
}
Let’s now ask Copilot to make the above code asynchronous.
Copilot produces the following yet appears to change the SQL from a raw string literal to a verbatim string.
public async Task<List<int>> CategoryIdentifiersAsync()
{ await using SqlConnection cn = new() { ConnectionString = ConnectionString }; return (await cn.QueryAsync<int>(@" SELECT DISTINCT CategoryID FROM dbo.Categories ")).AsList();
}
ReSharper will provide a refactor recommendation and the developer accepts the recommendation.
public async Task<List<int>> CategoryIdentifiersAsync()
{ await using SqlConnection cn = new() { ConnectionString = ConnectionString }; return (await cn.QueryAsync<int>("""
SELECT DISTINCT CategoryID FROM dbo.Categories """)).AsList();
}
Ask Copilot to write a unit test
- Select the above method
- Press ALT + /
- Enter
write a unit test
- ENTER
Note
We get a standard MS-Test but could specify which test framework to use.
[TestClass]
public class DapperOperationsTests
{ [TestMethod] public async Task CategoryIdentifiersAsync_ShouldReturnDistinctCategoryIDs() { // Arrange var dapperOperations = new DapperOperations(); // Act var result = await dapperOperations.CategoryIdentifiersAsync(); // Assert Assert.IsNotNull(result); CollectionAssert.AllItemsAreUnique(result); }
}
Ask Copilot to add error handling
Add error handling with SeriLog logging to this method
- Select the above method
- Press ALT + /
- Enter
add error handling with SeriLog logging to this method
- ENTER
The results were not what the developer wanted.
public async Task<List<int>> CategoryIdentifiersAsync()
{ try { await using SqlConnection cn = new() { ConnectionString = ConnectionString }; return (await cn.QueryAsync<int>(@" SELECT DISTINCT CategoryID FROM dbo.Categories ")).AsList(); } catch (Exception ex) { Log.Error(ex, "An error occurred while executing CategoryIdentifiersAsync"); throw; }
}
- We are back to a verbatim string, use ReSharper to use a raw string literal.
public async Task<List<int>> CategoryIdentifiersAsync()
{ try { await using SqlConnection cn = new() { ConnectionString = ConnectionString }; return (await cn.QueryAsync<int>("""
SELECT DISTINCT CategoryID FROM dbo.Categories """)).AsList();
} catch (Exception ex) { Log.Error(ex, "An error occurred while executing CategoryIdentifiersAsync"); throw; }
}
Let now go on without Copilot and get rid of throw
and change the return type to a tuple which can be deconstructed by the caller.
public async Task<(bool, List<int>)> CategoryIdentifiersAsync()
{ try { await using SqlConnection cn = new() { ConnectionString = ConnectionString }; return (true,(await cn.QueryAsync<int>("""
SELECT DISTINCT CategoryID FROM dbo.Categories """)).AsList());
} catch (Exception ex) { Log.Error(ex, "An error occurred while executing CategoryIdentifiersAsync"); return (false, null); }
}
Unit test revision
Now that there has been a change to the return type ask Copilot to write a new unit test
.
[TestClass]
public class DapperOperationsTests
{ [TestMethod] public async Task CategoryIdentifiersAsync_ShouldReturnCategoryIDs() { // Arrange DapperOperations dapperOperations = new DapperOperations(); // Act (bool success, List<int> categoryIds) = await dapperOperations.CategoryIdentifiersAsync(); // Assert Assert.IsTrue(success); Assert.IsNotNull(categoryIds); Assert.IsTrue(categoryIds.Count > 0); }
}
Unit test
In each example for unit test only one test was written, a developer should do a mirror image of a good test and have a one or more test for when things break.
Asking Copilot to write new code
Copilot can write code, for instance a developer needs a string language extension method that splits a string as follows, FirstName to First Name. The developer finds the following language extension but thinks there may be a more efficient way and not using regular expressions.
public static class StringExtensions
{ private static readonly Regex CamelCaseRegex = new(@"([A-Z][a-z]+)"); /// <summary> /// KarenPayne => Karen Payne /// </summary> [DebuggerStepThrough] public static string SplitCamelCase(this string sender) => string.Join(" ", CamelCaseRegex.Matches(sender) .Select(m => m.Value));
}
They ask several questions, but none are sufficient but than decide to include using span and the following is provided.
public static class StringExtensions
{ /// <summary> /// Split text at each capital letter /// </summary> /// <param name="input">string to work on</param> /// <returns> /// <para>An empty string, if the input is null or empty.</para> /// <para>Same as original if nothing affected</para> /// <para>String split on each uppercase token</para> /// <para>SSMS would become S S M S</para> /// </returns> [DebuggerStepThrough] public static string SplitCamelCase(this string input) { if (string.IsNullOrEmpty(input)) { return input; } Span<char> result = stackalloc char[input.Length * 2]; var resultIndex = 0; for (var index = 0; index < input.Length; index++) { var currentChar = input[index]; if (index > 0 && char.IsUpper(currentChar)) { result[resultIndex++] = ' '; } result[resultIndex++] = currentChar; } return result[..resultIndex].ToString(); }
}
Note
Of course, some developers favor shorter code as in the first code written yet shorter code is not always the best so if curious run both through Benchmarks.
Interface and classes
The following is a very simple example for using AI to create interfaces and classes in C# using GitHub Copilot and JetBrains ReSharper.
Note
Jetbrains ReSharper is a paid extension for Visual Studio. It is used here to move the generated code to proper folders and update the namespaces. Also, Jetbrains has a paid AI service which Karen has a ticket for.
Here we want to create
- A generic interface
- Customer class to implement above interface
- Both of the above get created in Program.cs using
Copilot
.
✔️ The interface are then moved using Jetbrain’s ReSharper Visual Studio extension to proper folders.
✔️ During the ReSharper action Visual Studio kicks in and asked to update the namespace.
A console project is used for this experiment in Program.cs.
Question 1
Create a public crud interface named IBase under Program.cs
Copilot responds with the following.
namespace InterfacesClassesExamples
{ public interface IBase<T> where T : class { T GetById(int id); IEnumerable<T> GetAll(); void Add(T entity); void Update(T entity); void Delete(T entity); } internal partial class Program { static void Main(string[] args) { } }
}
Question 2
Create a public class named Customer which implements IBase with the following properties, Id, FirstName, LastName, Email and BirthDate as DateOnly and implement INotifyPropertyChanged for each property.
Copilot responds with the following.
namespace InterfacesClassesExamples
{ public interface IBase<T> where T : class { T GetById(int id); IEnumerable<T> GetAll(); void Add(T entity); void Update(T entity); void Delete(T entity); } public class Customer : IBase<Customer>, INotifyPropertyChanged { private int _id; private string _firstName; private string _lastName; private string _email; private DateOnly _birthDate; public int Id { get { return _id; } set { if (_id != value) { _id = value; OnPropertyChanged(nameof(Id)); } } } public string FirstName { get { return _firstName; } set { if (_firstName != value) { _firstName = value; OnPropertyChanged(nameof(FirstName)); } } } public string LastName { get { return _lastName; } set { if (_lastName != value) { _lastName = value; OnPropertyChanged(nameof(LastName)); } } } public string Email { get { return _email; } set { if (_email != value) { _email = value; OnPropertyChanged(nameof(Email)); } } } public DateOnly BirthDate { get { return _birthDate; } set { if (_birthDate != value) { _birthDate = value; OnPropertyChanged(nameof(BirthDate)); } } } public Customer GetById(int id) { // Implementation of GetById method throw new NotImplementedException(); } public IEnumerable<Customer> GetAll() { // Implementation of GetAll method throw new NotImplementedException(); } public void Add(Customer entity) { // Implementation of Add method throw new NotImplementedException(); } public void Update(Customer entity) { // Implementation of Update method throw new NotImplementedException(); } public void Delete(Customer entity) { // Implementation of Delete method throw new NotImplementedException(); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } internal partial class Program { static void Main(string[] args) { } }
}
Next steps, create an Interface and Models folder and move IBase to the Interface folder and Customer to the Model folder or copy IBase and Customer to appropriate folders in another project. ReSharper can assist with moving IBase and Customer to their own files and Visual Studio will assist with setting up proper namespacing.
Now suppose the above was done in the same console project but in a class outside of Program.cs the results vary.
In one case the following
public class Customer : IBase<Customer>, INotifyPropertyChanged
Is created as
public class Customer : IBase<int>, INotifyPropertyChanged
In another test in a separate class the following question was posed to Copilot.
Create a public crud generic interface named IBase with a constraint on T. create a public class named Customer which implements IBase with the following properties, Id, FirstName, LastName, Email and BirthDate as DateOnly and implement INotifyPropertyChanged for each property
The response was missing private fields for property change notification and IBase was not implemented and duplicate property names.
namespace InterfacesClassesExamples.Test
{ public interface IBase<T> where T : class { void Create(T entity); T Read(int id); void Update(T entity); void Delete(int id); } public class Customer : IBase<Customer>, INotifyPropertyChanged { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public DateOnly BirthDate { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // Implement INotifyPropertyChanged for each property public string FirstName { get { return _firstName; } set { if (_firstName != value) { _firstName = value; OnPropertyChanged(nameof(FirstName)); } } } // Implement INotifyPropertyChanged for each property public string LastName { get { return _lastName; } set { if (_lastName != value) { _lastName = value; OnPropertyChanged(nameof(LastName)); } } } // Implement INotifyPropertyChanged for each property public string Email { get { return _email; } set { if (_email != value) { _email = value; OnPropertyChanged(nameof(Email)); } } } // Implement INotifyPropertyChanged for each property public DateOnly BirthDate { get { return _birthDate; } set { if (_birthDate != value) { _birthDate = value; OnPropertyChanged(nameof(BirthDate)); } } } }
}
Next, still in the same class as above.
- Create a public crud interface named IBase
- Create a public class named Customer which implements IBase with the following properties, Id, FirstName, LastName, Email and BirthDate as DateOnly and implement INotifyPropertyChanged for each property.
Results with no change property notification code.
namespace InterfacesClassesExamples.Test
{ public interface IBase<T> where T : class { T GetById(int id); void Add(T entity); void Update(T entity); void Delete(T entity); } public class Customer : IBase<Customer>, INotifyPropertyChanged { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public DateOnly BirthDate { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public Customer GetById(int id) { // Implementation of GetById method } public void Add(Customer entity) { // Implementation of Add method } public void Update(Customer entity) { // Implementation of Update method } public void Delete(Customer entity) { // Implementation of Delete method } }
}
Next, here the experiment was done in a new class project.
Ask Copilot the same questions as in prior failures and failed again.
Instead of
public class Customer : IBase<Customer>, INotifyPropertyChanged
Copilot produced.
public class Customer : IBase, INotifyPropertyChanged
Then when ready to try again the following was suggested without property change notification.
Let’s try the last question as follows.
_Create a public class named Customer which implements IBase with the following properties, Id, FirstName, LastName, Email and BirthDate as DateOnly and implement INotifyPropertyChanged for each property
_
And we get. Next step, move Customer to it’s own file.
using System.ComponentModel; namespace InterfacesClassesExamplesLibrary.Interfaces
{ public class Customer : IBase<Customer>, INotifyPropertyChanged { private int _id; private string _firstName; private string _lastName; private string _email; private DateOnly _birthDate; public int Id { get { return _id; } set { if (_id != value) { _id = value; OnPropertyChanged(nameof(Id)); } } } public string FirstName { get { return _firstName; } set { if (_firstName != value) { _firstName = value; OnPropertyChanged(nameof(FirstName)); } } } public string LastName { get { return _lastName; } set { if (_lastName != value) { _lastName = value; OnPropertyChanged(nameof(LastName)); } } } public string Email { get { return _email; } set { if (_email != value) { _email = value; OnPropertyChanged(nameof(Email)); } } } public DateOnly BirthDate { get { return _birthDate; } set { if (_birthDate != value) { _birthDate = value; OnPropertyChanged(nameof(BirthDate)); } } } public Customer GetById(int id) { // Implementation of GetById method } public void Insert(Customer entity) { // Implementation of Insert method } public void Update(Customer entity) { // Implementation of Update method } public void Delete(Customer entity) { // Implementation of Delete method } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public interface IBase<T> { T GetById(int id); void Insert(T entity); void Update(T entity); void Delete(T entity); }
}
Lessons learn for generating new code
- Location of where the question is asked determines how copilot will respond and how the question is asked will also determine copilot’s response.
- Sometimes it is faster to just write the code by hand if responding solutions are not to a developers requirements.
First out questions in a text file, ask Copilot, if the response is not proper, go back and create a copy of the question in the text file and rephrase. Try until a good enough response is provided or go old school and write code yourself.
Debugging
When GitHub Copilot is active and a runtime exception is raised a developer can click, Ask Copilot to get suggestions which is what the project is for. Once asking coilot a window opens with recommendations and in most cases why the exception was thrown.
The connection string is deliberately setup to point to a non-existing SQL-Server instance and the default time out has been altered from the original which is 30 seconds down to two seconds.
Note
This project was created to show a simple example for SQL-Server computed columns and was put here to show the above.
About (When the project has no issues)
Provides an interesting way to compute how old a person is in years.
Original code came from this Stackoverflow post.
I took the code and created a simple Console project to demonstrate how to use it with a computed column in a SQL-Server database table using Dapper to read the data.
CREATE TABLE [dbo].[BirthDays]( [Id] [int] IDENTITY(1,1) NOT NULL, [FirstName] [nvarchar](max) NULL, [LastName] [nvarchar](max) NULL, [BirthDate] [date] NULL, [YearsOld] AS ((CONVERT([int],format(getdate(),'yyyyMMdd'))-CONVERT([int],format([BirthDate],'yyyyMMdd')))/(10000))
- Take birthdate and current date, format and convert to integer
- Subtract birthdate from current date
- Divide by 10,000 to get years old
Source code
Do not expect the code provided to be stable as it was used for demonstrating what has been provided above.
VS Code
Using GitHub Copilot works great here as well as Visual Studio.
The shortcut is CTRL + I rather than ALT + / in Visual Studio.
A great usage is fixing code, in the screenshot below there are errors on each input because they are not associated with labels. Hover and a menu appears, use the last menu item to see why this is an issue (a WCAG AA rule) and second from bottom to fix.
Let’s fix the issue with copilot by associating labels above inputs with the proper input and for kicks, surround the labels and inputs with a fieldset.
Press CTRL + I and ask with
wrap in fieldset and legend then associate inputs with labels
Copilot History
In VS Code, use the arrow keys to traverse history for the current session while Visual Studio does not have history available.
Security
Use caution to what text is placed into your Copilot questions as they very well be seen by other users of Copilot. In one case while writing documentation into a markdown file Copilot suggested text that was from a closed system. They were informed that this happened. Imagine exposing customer information or internal server details and a bad actor got the information what might happen?
Summary
Made it this far? That means by applying what was written the reader will have a better expectation of Copilot. Copilot is a great edition to programmers at every level but one should not rely solely on Copilot as seen not all response will be correct. As Copilot matures expect better responses and more features.