Github Copilot Chat 初體驗

2023-05-17 06:00:29

最近因為陽了的緣故一直躺在床上。今天終於從床上爬起來了。不是因為好透了,而是因為我收到了申請Copilot Chat preview 許可權通過的郵件。實在忍不住,於是起床開電腦在咳嗽聲中進行了一番體驗。

如何申請

https://github.com/github-copilot/chat_waitlist_signup/join

安裝外掛

申請通過後,郵件上就有說明。外掛有2個,一個是基於 VSCode 的,一個是基於 Visual Studio 的。今天使用的是 Visual Studio 版本。
在 Marketplace 搜尋 Visual Studio Extension for GitHub Copilot Preview 下載安裝重啟 VS 即可。

使用

在安裝完外掛後,隨便開啟一個專案,隨便選擇一個類,右鍵就有"詢問 Copilot" 選單:

點選選單後,下面就會出現一個對話方塊:

使用者可以在對話方塊進行提問

同時在側邊欄也會多出一個 Github Copilot chat 的 tab。

對話/問答

Copilot 的基本能力,比如寫單元測試等,其實在以前的文章已經演示過了,這次不在重複。既然新版叫 Copilot Chat 那麼肯定是跟 ChatGPT 一樣是問答模式了。下面就來跟 Copilot 嘮嘮嗑。

minjie:
Copilot Chat 是什麼?
Copilot:

Copilot Chat是 Microsoft的GitHub Copilot AI的一種特性,允許開發人員使用文字輸入與AI互動,並實時獲得程式碼建議和補全。Copilot Chat是一種旨在通過提供基於檢測到的程式碼模式的建議來增強開發人員的生產力的工具。Copilot Chat使用機器學習演演算法識別模式併為各種程式設計活動建議程式碼片段。它還具有學習上一個使用者互動的能力,隨著時間的推移將變得更加準確。如果您需要使用Copilot Chat獲取幫助進行開發工作,請在有具體問題的情況下描述清晰問題的場景與具體需求,以便我更好地幫助您。

minjie:
請問 c# int 的最大值是多少
Copilot:

The maximum value of an int in C# is 2,147,483,647 (or 0x7FFFFFFF in hexadecimal). This is because an int in C# is a signed 32-bit integer, meaning it can hold values between -2,147,483,648 and 2,147,483,647 inclusive.
Here's an example of assigning int.MaxValue to a variable in C#:
int maxVal = int.MaxValue;
This creates a new variable called maxVal of type int and assigns it the value of the maximum possible integer.

minjie:
在 dotnet 中一般怎麼建立 websocket 連線,給個範例
Copilot:

using System.Net.WebSockets;
using System.Threading.Tasks;

public async Task ConnectWebSocketAsync(string url) {
    using (ClientWebSocket ws = new ClientWebSocket()) {
        await ws.ConnectAsync(new System.Uri(url), CancellationToken.None);
        await Task.WhenAll(Receive(ws), Send(ws));
    }    
}

private static async Task Receive(ClientWebSocket ws) {
    byte[] buffer = new byte[1024 * 4];
    while (ws.State == WebSocketState.Open) {
        var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
        if (result.MessageType == WebSocketMessageType.Close) {
            await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
        } else {
            // Process received data.
        }
    }
}

private static async Task Send(ClientWebSocket ws) {
    while (ws.State == WebSocketState.Open) {
        // Get data to send.
        byte[] buffer = new byte[1024 * 4];
        var result = await ws.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
        await Task.Delay(2000);
    }
}

問答可以說問啥答啥,基本上毫無破綻。

對程式碼進行解釋

Copilot Chat 的另外一個能力就是對一段程式碼進行解釋
比如讓它對 AgileConfigClient 的核心類 ConfigClient 進行解釋。首先開啟 AgileConfigClient 專案,然後選中 ConfigClient 類檔案,然後就可以問了。

minjie:
請問 ConfigClient 類是用來幹嘛的?
Copilot:

ConfigClient is a class that allows the management and retrieval of configuration data. The class has methods that load configurations from a server and save them to a local storage. The configuration is typically represented in key/value pairs. The Get method is used to retrieve a particular key, and the GetGroup method is used to retrieve all keys under a common group name.
Here's an example usage of the ConfigClient class:
var config = new ConfigClient();
var result = config.Get("keyName");
This creates a new instance of the ConfigClient object and retrieves the value associated with the specified "keyName" string.

Copilot 準確的做出瞭解釋,只是使用了英文,我讓它使用中文,它不理我。

使用 Copilot Chat 一步步生成一個 To-do List 的 webapi 專案

下面的內容演示瞭如何使用 Copilot chat 一步步通過對話方式生成一個可以執行的專案。在最後我甚至想讓它直接寫個文章,Copilot 也寫出來了,不過反正見過了 ChatGPT 的能力了也不奇怪哈。

minjie:
生成一個ToDo實體類
Copilot:

public class ToDo
{
    public int Id { get; set; }
    public string Task { get; set; }
}

minjie:
新增一個欄位 Completed
Copilot:

public class ToDo
{
    public int Id { get; set; }
    public string Task { get; set; }
    public bool Completed { get; set; }
}

minjie:
對這個實體類生產資料庫存取類,使用 efcore
Copilot:

using Microsoft.EntityFrameworkCore;
using System;

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options)
    { }

    public DbSet<MyEntity> MyEntities { get; set; }
}

minjie:
改成 TodoContext
Copilot:

using Microsoft.EntityFrameworkCore;
using System;

public class TodoContext : DbContext
{
    public TodoContext(DbContextOptions<TodoContext> options) : base(options)
    { }

    public DbSet<ToDo> ToDos { get; set; }
}

minjie:
支援 sqlite
Copilot:

using Microsoft.EntityFrameworkCore;
using System;

public class TodoContext : DbContext
{
    public TodoContext(DbContextOptions<TodoContext> options) : base(options)
    { }

    public DbSet<ToDo> ToDos { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite("Data Source=todos.db");
}

minjie:
刪除建構函式
Copilot:

using Microsoft.EntityFrameworkCore;
using System;

public class TodoContext : DbContext
{
    public DbSet<ToDo> ToDos { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite("Data Source=todos.db");
}

minjie:
使用 ToDoContext 生成一個 ToDoService 類,這個類包裝了該刪查方法
Copilot:

public class ToDoService
{
    private readonly TodoContext _context;

    public ToDoService(TodoContext context)
    {
        _context = context;
    }

    public List<ToDo> GetToDos()
    {
        return _context.ToDos.ToList();
    }

    public ToDo GetToDoById(int id)
    {
        return _context.ToDos.Find(id);
    }

    public void AddToDo(ToDo todo)
    {
        _context.ToDos.Add(todo);
        _context.SaveChanges();
    }

    public void UpdateToDoById(int id, ToDo todo)
    {
        var existingTodo = _context.ToDos.Find(id);

        if (existingTodo != null)
        {
            existingTodo.Task = todo.Task;
            existingTodo.Completed = todo.Completed;

            _context.SaveChanges();
        }
    }

    public void RemoveToDoById(int id)
    {
        var existingTodo = _context.ToDos.Find(id);

        if (existingTodo != null)
        {
            _context.ToDos.Remove(existingTodo);
            _context.SaveChanges();
        }
    }
}

minjie:
把所有方法改成非同步
Copilot:

using CopilotChatPreview;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

public class ToDoService
{
    private readonly TodoContext _context;

    public ToDoService(TodoContext context)
    {
        _context = context;
    }

    public async Task<List<ToDo>> GetToDos()
    {
        return await _context.ToDos.ToListAsync();
    }

    public async Task<ToDo> GetToDoById(int id)
    {
        return await _context.ToDos.FindAsync(id);
    }

    public async Task AddToDoAsync(ToDo todo)
    {
        _context.ToDos.Add(todo);
        await _context.SaveChangesAsync();
    }

    public async Task UpdateToDoByIdAsync(int id, ToDo todo)
    {
        var existingTodo = await _context.ToDos.FindAsync(id);

        if (existingTodo != null)
        {
            existingTodo.Task = todo.Task;
            existingTodo.Completed = todo.Completed;

            await _context.SaveChangesAsync();
        }
    }

    public async Task RemoveToDoByIdAsync(int id)
    {
        var existingTodo = await _context.ToDos.FindAsync(id);

        if (existingTodo != null)
        {
            _context.ToDos.Remove(existingTodo);
            await _context.SaveChangesAsync();
        }
    }
}

minjie:
生產一個控制器並且使用 ToDoService,使用 restful 方式暴露出去
Copilot:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class ToDoController : ControllerBase
{
    private readonly ToDoService _toDoService;

    public ToDoController(ToDoService toDoService)
    {
        _toDoService = toDoService;
    }

    [HttpGet]
    public async Task<ActionResult<List<ToDo>>> GetAllToDos()
    {
        return await _toDoService.GetToDos();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<ToDo>> GetToDoById(int id)
    {
        var todo = await _toDoService.GetToDoById(id);

        if (todo == null)
        {
            return NotFound();
        }

        return todo;
    }

    [HttpPost]
    public async Task<IActionResult> CreateToDoAsync(ToDo todo)
    {
        await _toDoService.AddToDoAsync(todo);

        return CreatedAtAction(nameof(GetToDoById), new { id = todo.Id }, todo);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateToDoByIdAsync(int id, ToDo todo)
    {
        await _toDoService.UpdateToDoByIdAsync(id, todo);

        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteToDoByIdAsync(int id)
    {
        await _toDoService.RemoveToDoByIdAsync(id);

        return NoContent();
    }
}

minjie:
在程式啟動的時候把ToDoService註冊到容器並且使用scope生命週期
Copilot:

// In Startup.cs file, add the following code to ConfigureServices method
services.AddScoped<ToDoService>();

minjie:
可以把以上對話整理成一篇文章嗎?文章的題目叫 CopilotChat 初體驗

CopilotChat 初體驗
在這篇文章中,我們將一起學習如何使用 Microsoft 的 AI 程式設計助手,GitHub Copilot 來生成 C# 程式碼。同時,我們將使用一個 To-Do 應用來展示 Copilot 的功能。
註冊 Copilot
要使用 GitHub Copilot,你需要註冊並安裝 GitHub Copilot 擴充套件。在安裝時,你需要登入你的 GitHub 帳戶和選擇你喜歡的語言。
準備工作
我們將使用 Entity Framework Core 和 ASP.NET Core 建立 To-Do 應用程式。以下是建立應用程式的基本步驟。
步驟 1:建立資料庫上下文
我們首先需要建立資料上下文。我們建立一個名為 TodoContext 的類,在其中我們將 ToDo 實體框架建立為資料庫表。同時還將新增一個連線字串以及一些模型構建程式碼。
using Microsoft.EntityFrameworkCore;

namespace CopilotChatPreview
{
    public class TodoContext : DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options) : base(options) {}

        public DbSet<ToDo> ToDos { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ToDo>().HasData(
                new ToDo
                {
                    Id = 1,
                    Task = "Learn AI programming",
                    Completed = false
                },
                new ToDo
                {
                    Id = 2,
                    Task = "Build a chatbot",
                    Completed = false
                });
        }
    }
}


步驟 2:建立服務類
我們建立一個 ToDoService 類,用於在資料庫中執行增刪改查操作。
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

public class ToDoService
{
    private readonly TodoContext _context;

    public ToDoService(TodoContext context)
    {
        _context = context;
    }

    ...

    public async Task RemoveToDoByIdAsync(int id)
    {
        var existingTodo = await _context.ToDos.FindAsync(id);

        if (existingTodo != null)
        {
            _context.ToDos.Remove(existingTodo);
            await _context.SaveChangesAsync();
        }
    }
}

步驟 3:建立 To-Do 控制器
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class ToDoController : ControllerBase
{
    private readonly ToDoService _toDoService;

    public ToDoController(ToDoService toDoService)
    {
        _toDoService = toDoService;
    }

    ...

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteToDoByIdAsync(int id)
    {
        await _toDoService.RemoveToDoByIdAsync(id);

        return NoContent();
    }
}

步驟 4:註冊服務
在 ConfigureServices 方法中,我們需要將資料庫上下文和服務類註冊到容器中。
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddDbContextPool<TodoContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddScoped<ToDoService>();
    ...
}

總結
在這篇文章裡,我們介紹瞭如何使用 AI 程式設計助手 Copilot 來生成 C# 程式碼。我們還介紹瞭如何使用 Entity Framework Core 建立和操縱資料庫表。通過這篇文章,你已經開始掌握瞭如何使用 AI 程式設計助手來讓開發 C# 應用程式變得更加輕鬆和高效。

總結

本想讓 Copilot 幫我總結一下,最後還是我自己來總結吧。本次初步體驗了 Copilot chat 的一些基本能力。比如如何問答,如何分析程式碼。特別是最後一步步全程使用對話方式生成一個可以執行的專案的內容,還是挺精彩的。作為一個 AI 助手是完全合格的。不過說實話沒有第一次使用 Copilot 的時候的那種震撼。為啥?因為 ChatGPT 珠玉在前,短短的一段時間,已經讓我們把對 AI 的期望拉到了前所未有的高度。Copilot chat 作為 ChatGPT 模型在垂直領域的實踐,還需好好打磨,讓我們期待一下正式版吧。