Exploring GitHub Copilot with Microsoft Visual Studio
Tags: ai, productivity
Published: 2024-06-09 20:07:55

## 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.

:heavy_check_mark: Do not have a Copilot subscription, [go here](https://github.com/settings/copilot) for a free trial.



### 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 <kbd>F2</kbd>)
- Create unit test
- Assist with debugging code
- Explaining code
- Documenting code

The following table was copied from [this page](https://github.blog/2024-03-25-how-to-use-github-copilot-in-your-ide-tips-tricks-and-best-practices/) in raw format then asked Copilot to create a three column markdown table :green_heart:

| 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 |

## How GitHub Copilot handles data

As you explore adopting GitHub Copilot, you might wonder what data it collects, how that data is used, transferred, stored, and where its lifecycle ends. In this guide, we’ll describe GitHub Copilot’s data pipeline, and explain how your data is kept safe while being used to provide the most accurate code suggestions and responses possible.

Read [full article](https://resources.github.com/learn/pathways/copilot/essentials/how-github-copilot-handles-data/) at GitHub

## 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.

```csharp
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.


![shown with Intellisense](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aerefangybvep51n927r.png)

Using /doc feature of GitHub Copilot

```csharp
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.

![proper documentation from Copilot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vg8r04q8lwhwdxvirm0.png)

### Example 2

A developer comes across the following method has been asked to document the method.

```csharp
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.

1. Select the method
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. Type <kbd>/</kbd> which provides options, select **doc**
1. Press <kbd>ENTER</kbd>

Results

```csharp
/// <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.

```csharp
string value = NumbersPattern().Match(sender).Value;

return sender[..^value.Length] + (long.Parse(value) + incrementBy)
    .ToString().PadLeft(value.Length, '0');
```

1. Select the following body of code.
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. Press <kbd>/</kbd>, select **explain**
1. Press <kbd>ENTER</kbd>

The following is provided and at the bottom additional help.


![Helpful information at bottom of explain dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/teqwc0hozqalee0rxvov.png)

The selected code is a part of the NextValue method in the Helpers class. Let's break it down step by step:
1.	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.
2.	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.

```javascript
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:
1.	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.
2.	const xhr = new XMLHttpRequest();: This line creates a new XMLHttpRequest object. The XMLHttpRequest object is used to interact with servers and make HTTP requests.
3.	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.
4.	xhr.send();: This line sends the HTTP request to the server.
5.	xhr.responseType = "json";: This line sets the expected response type to JSON. This tells the browser to automatically parse the response as JSON.
6.	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.
7.	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).
8.	const data = xhr.response;: This line assigns the response data to the data variable.
9.	document.getElementById("people-template").innerHTML = "";: This line clears the existing content of the "people-template" element.
10.	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.
11.	$('#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 (<li>) for each user. The user's ID, name, and email are displayed in the list item.
12.	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.

```css
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**

![Results from Copilot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e447x6l5379p862ucemj.png)

What about SQL?

A developer comes across the following and wants to understand the statement.

```sql
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];
```

1. Select the statment
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. <kbd>ENTER</kbd>
1. `/explain`
1. Press <kbd>ENTER</kbd>

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.

![suggestion from above ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rlcqw13t2mdankuf57ma.png)

What about documenting the statement.

1. Select the statment
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. <kbd>ENTER</kbd>
1. `/doc`
1. Press <kbd>ENTER</kbd>


```sql
/*
    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

```csharp
public string[] Items()
{
    return new[] { ApplicationName, ApplicationVersion, ApplicationKey };
}
```

### Refactor to expression body

```csharp
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](https://github.com/DapperLib/Dapper) and wants to rewrite this code using Dapper.

```csharp
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;
}
```

1. Select the above method
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. Enter `Refactor this method to use Dapper.`
1. <kbd>ENTER</kbd>

The following is produced.

```csharp
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.

```csharp
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.


```csharp
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.

```csharp
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

1. Select the above method
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. Enter `write a unit test`
1. <kbd>ENTER</kbd>

> **Note**
> We get a standard MS-Test but could specify which test framework to use.

```csharp
[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

1. Select the above method
1. Press <kbd>ALT</kbd>  + <kbd>/</kbd>
1. Enter `add error handling with SeriLog logging to this method`
1. <kbd>ENTER</kbd>

The results were not what the developer wanted.

```csharp
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.

```csharp
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](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct) by the caller.

```csharp
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`.

```csharp
[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.

```csharp
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.

```csharp
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`.

:heavy_check_mark: The interface are then moved using Jetbrain's ReSharper Visual Studio extension to proper folders.

:heavy_check_mark: 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.

```csharp
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.

```csharp
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

```csharp
public class Customer : IBase<Customer>, INotifyPropertyChanged
```
Is created as

```csharp
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.

```csharp
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.

1. Create a public crud interface named IBase
1. 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.

```csharp
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 

```csharp
public class Customer : IBase<Customer>, INotifyPropertyChanged
```

Copilot produced.

```charp
public class Customer : IBase, INotifyPropertyChanged
```

Then when ready to try again the following was suggested without property change notification.


![copilot suggestion without asking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5yrle0f6uzlchdk2vau.png)


![Wait a minute](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80yb7bk5u597kjn9kog3.png)

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.

```charp
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.


![Tip](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlpuzl0u3t9l0shmwwic.png)

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.



![Ask copilot while debugging](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5255ixuuxlgws650f17.png)


> **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](https://stackoverflow.com/a/11942/5509738).

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.

```sql
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.

{% cta https://github.com/karenpayneoregon/Generative-Artificial-Intelligence %} Source code {% endcta %} 

## VS Code

Using GitHub Copilot works great here as well as Visual Studio.

The shortcut is <kbd>CTRL</kbd>  + <kbd>I</kbd> rather than <kbd>ALT</kbd>  + <kbd>/</kbd> 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.


![VS Code showing part of an html file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m47z5lm1imukf68wekho.png)

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 <kbd>CTRL</kbd>  + <kbd>I</kbd> and ask with 

_wrap in fieldset and legend then associate inputs with labels_


![before and after asking copilot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nygihhpd4lgyqhn87re7.png)

## Copilot History

In VS Code, use the arrow keys to traverse history for the current session while Visual Studio does not have history available.

## Generate commit messages
Not sure what to write for a commit message? Click the pencell and Copilot will write the message for you.

![Shows commit screen for GitHub in Visual Studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mg8m06yu06l3k072a57.png) 

## Unravel Your Commit History with GitHub Copilot
From Microsoft [here](https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes#unravel-your-commit-history-with-github-copilot)

Git history can be daunting to shuffle through, but it's often the best way to learn about a code base or help identify the origin of a bug. We've added a GitHub Copilot powered explain feature to the Commit Details window to make it easier to understand the contents of each commit.

You'll need to have an active GitHub Copilot subscription and the GitHub Copilot Chat Extension installed. Double click on any commit to open the Commit Details pane in the Git Repository window. Then, click on the 'Explain Commit' sparkle pen icon to get a summary of the changes side by side with the code.

## Copilot options in Visual Studio

Top right hand corner of the IDE, click on GitHub Copilot than settings.

![Copilot options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f5eu0h1j9khq8wi1tqu.png)

## Copilot options in Visual Studio Code

- From Setting, type copilot into the search input.

Also for commands press <kbd>F1</kbd> and type copilot.


![Copilot commands for VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjuq2y3m9n5kl2p97ogu.png)



## 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.


## Resources

- [What GitHub Copilot can do for your organization](https://resources.github.com/learn/pathways/copilot/essentials/what-github-copilot-can-do-for-your-organization/)
- [How to use GitHub Copilot: Prompts, tips, and use cases](https://github.blog/2023-06-20-how-to-write-better-prompts-for-github-copilot/)
- [GitHub Copilot Trust Center](https://resources.github.com/copilot-trust-center/)
- Microsoft learn: [Training modules](https://learn.microsoft.com/en-us/training/browse/?terms=github%20copilot)
- Research: [Quantifying GitHub Copilot’s impact on code quality](https://github.blog/2023-10-10-research-quantifying-github-copilots-impact-on-code-quality/)
- [Building Generative AI apps with .NET 8](https://devblogs.microsoft.com/dotnet/build-gen-ai-with-dotnet-8/)
- [AI for .NET developers](https://learn.microsoft.com/en-us/dotnet/ai/)

### Visual Studio specific

_Currently in preview_

- [GitHub Copilot is getting smarter](https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes-preview#githubcopilotisgettingsmarter) 
- [Naming things made easy](https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes-preview#airenamesuggestions)
- [AI-generated breakpoint expressions](https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes-preview#aigeneratedbreakpointscpp)

## Learning videos

{% embed https://www.youtube.com/watch?v=dhfTaSGYQ4o %}