20210107のC#に関する記事は5件です。

AWS CloudShell に .NET 5 をインストールして、Windows向けの実行ファイルを作ってみる

はじめに

AWS CloudShellはAWSのマネジメントコンソール上で、Amazon Linux2を起動してAWSの各機能をコマンドラインベースで実行するための機能です。Amazon Linux2はCentOS7に近い環境なので、yumを使って.NET Coreをインストールすることができます。

今回はAWS CloudShell上に.NET 5をインストールして、アプリをビルド→実行→Windows向けの実行ファイルのダウンロードをしてみます。
# だれがうれしいんだ!?という内容ですが、、、

CloudShellの起動と.NET Core5のインストール

AWS マネジメントコンソールにログインして >_ なアイコンをクリックしてCloudShellを立ち上げます。
image.png

あとは、CentOS に .NET SDK または .NET ランタイムをインストールするの、CentOS7向けの手順を実行してセットアップしていきます。
まずはリポジトリにMicrosoftのリポジトリを追加して

CloudShell
$ sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
Retrieving https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
warning: waiting for transaction lock on /var/lib/rpm/.rpm.lock
Preparing...                          ################################# [100%]
Updating / installing...
   1:packages-microsoft-prod-1.0-1    ################################# [100%]

インストールを実行すると、実際にパッケージをダウンロードするタイミングと、インストールの前に確認を求められるので両方ともyで応答するればインストールは完了です。

CloudShell
$  sudo yum install dotnet-sdk-5.0
Loaded plugins: ovl, priorities
packages-microsoft-com-prod                                               | 3.0 kB  00:00:00     
packages-microsoft-com-prod/primary_db                                    | 278 kB  00:00:00     
Resolving Dependencies
--> Running transaction check
---> Package dotnet-sdk-5.0.x86_64 0:5.0.101-1 will be installed
--> Processing Dependency: netstandard-targeting-pack-2.1 >= 2.1.0 for package: dotnet-sdk-5.0-5.0.101-1.x86_64
--> Processing Dependency: dotnet-runtime-5.0 for package: dotnet-sdk-5.0-5.0.101-1.x86_64
--> Processing Dependency: dotnet-targeting-pack-5.0 for package: dotnet-sdk-5.0-5.0.101-1.x86_64
--> Processing Dependency: dotnet-apphost-pack-5.0 for package: dotnet-sdk-5.0-5.0.101-1.x86_64
--> Processing Dependency: aspnetcore-runtime-5.0 for package: dotnet-sdk-5.0-5.0.101-1.x86_64
--> Processing Dependency: aspnetcore-targeting-pack-5.0 for package: dotnet-sdk-5.0-5.0.101-1.x86_64
--> Running transaction check
---> Package aspnetcore-runtime-5.0.x86_64 0:5.0.1-1 will be installed
---> Package aspnetcore-targeting-pack-5.0.x86_64 0:5.0.0-1 will be installed
---> Package dotnet-apphost-pack-5.0.x86_64 0:5.0.1-1 will be installed
---> Package dotnet-runtime-5.0.x86_64 0:5.0.1-1 will be installed
--> Processing Dependency: dotnet-hostfxr-5.0 >= 5.0.1 for package: dotnet-runtime-5.0-5.0.1-1.x86_64
--> Processing Dependency: dotnet-runtime-deps-5.0 >= 5.0.1 for package: dotnet-runtime-5.0-5.0.1-1.x86_64
---> Package dotnet-targeting-pack-5.0.x86_64 0:5.0.0-1 will be installed
---> Package netstandard-targeting-pack-2.1.x86_64 0:2.1.0-1 will be installed
--> Running transaction check
---> Package dotnet-hostfxr-5.0.x86_64 0:5.0.1-1 will be installed
--> Processing Dependency: dotnet-host >= 5.0.1 for package: dotnet-hostfxr-5.0-5.0.1-1.x86_64
---> Package dotnet-runtime-deps-5.0.x86_64 0:5.0.1-1 will be installed
--> Running transaction check
---> Package dotnet-host.x86_64 0:5.0.1-1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

=================================================================================================
 Package                           Arch      Version        Repository                      Size
=================================================================================================
Installing:
 dotnet-sdk-5.0                    x86_64    5.0.101-1      packages-microsoft-com-prod     80 M
Installing for dependencies:
 aspnetcore-runtime-5.0            x86_64    5.0.1-1        packages-microsoft-com-prod    8.0 M
 aspnetcore-targeting-pack-5.0     x86_64    5.0.0-1        packages-microsoft-com-prod    2.1 M
 dotnet-apphost-pack-5.0           x86_64    5.0.1-1        packages-microsoft-com-prod    4.6 M
 dotnet-host                       x86_64    5.0.1-1        packages-microsoft-com-prod     64 k
 dotnet-hostfxr-5.0                x86_64    5.0.1-1        packages-microsoft-com-prod    170 k
 dotnet-runtime-5.0                x86_64    5.0.1-1        packages-microsoft-com-prod     29 M
 dotnet-runtime-deps-5.0           x86_64    5.0.1-1        packages-microsoft-com-prod    2.8 k
 dotnet-targeting-pack-5.0         x86_64    5.0.0-1        packages-microsoft-com-prod    3.1 M
 netstandard-targeting-pack-2.1    x86_64    2.1.0-1        packages-microsoft-com-prod    2.1 M

Transaction Summary
=================================================================================================
Install  1 Package (+9 Dependent packages)

Total download size: 129 M
Installed size: 369 M
Is this ok [y/d/N]: y
Downloading packages:
warning: /var/cache/yum/x86_64/2/packages-microsoft-com-prod/packages/aspnetcore-targeting-pack-5.0.0.rpm: Header V4 RSA/SHA256 Signature, key ID be1229cf: NOKEY
Public key for aspnetcore-targeting-pack-5.0.0.rpm is not installed
(1/10): aspnetcore-targeting-pack-5.0.0.rpm                               | 2.1 MB  00:00:00     
(2/10): dotnet-apphost-pack-5.0.1-x64.rpm                                 | 4.6 MB  00:00:00     
(3/10): dotnet-host-5.0.1-x64.rpm                                         |  64 kB  00:00:00     
(4/10): dotnet-hostfxr-5.0.1-x64.rpm                                      | 170 kB  00:00:00     
(5/10): aspnetcore-runtime-5.0.1-x64.rpm                                  | 8.0 MB  00:00:01     
(6/10): dotnet-runtime-deps-5.0.1-centos.7-x64.rpm                        | 2.8 kB  00:00:00     
(7/10): dotnet-runtime-5.0.1-x64.rpm                                      |  29 MB  00:00:01     
(8/10): dotnet-targeting-pack-5.0.0-x64.rpm                               | 3.1 MB  00:00:00     
(9/10): netstandard-targeting-pack-2.1.0-x64.rpm                          | 2.1 MB  00:00:00     
(10/10): dotnet-sdk-5.0.101-x64.rpm                                       |  80 MB  00:00:09     
-------------------------------------------------------------------------------------------------
Total                                                             11 MB/s | 129 MB  00:00:11     
Retrieving key from https://packages.microsoft.com/keys/microsoft.asc
Importing GPG key 0xBE1229CF:
 Userid     : "Microsoft (Release signing) <gpgsecurity@microsoft.com>"
 Fingerprint: bc52 8686 b50d 79e3 39d3 721c eb3e 94ad be12 29cf
 From       : https://packages.microsoft.com/keys/microsoft.asc
Is this ok [y/N]: y
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
  Installing : dotnet-targeting-pack-5.0-5.0.0-1.x86_64                                     1/10 
  Installing : aspnetcore-targeting-pack-5.0-5.0.0-1.x86_64                                 2/10 
  Installing : dotnet-host-5.0.1-1.x86_64                                                   3/10 
  Installing : dotnet-hostfxr-5.0-5.0.1-1.x86_64                                            4/10 
  Installing : dotnet-runtime-deps-5.0-5.0.1-1.x86_64                                       5/10 
  Installing : dotnet-runtime-5.0-5.0.1-1.x86_64                                            6/10 
  Installing : aspnetcore-runtime-5.0-5.0.1-1.x86_64                                        7/10 
  Installing : netstandard-targeting-pack-2.1-2.1.0-1.x86_64                                8/10 
  Installing : dotnet-apphost-pack-5.0-5.0.1-1.x86_64                                       9/10 
  Installing : dotnet-sdk-5.0-5.0.101-1.x86_64                                             10/10 
This software may collect information about you and your use of the software, and send that to Microsoft.
Please visit http://aka.ms/dotnet-cli-eula for more information.
Welcome to .NET!
---------------------
Learn more about .NET: https://aka.ms/dotnet-docs
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli-docs

Telemetry
---------
The .NET tools collect usage data in order to help us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

Configuring...
--------------
A command is running to populate your local package cache to improve restore speed and enable offline access. This command takes up to one minute to complete and only runs once.
  Verifying  : dotnet-hostfxr-5.0-5.0.1-1.x86_64                                            1/10 
  Verifying  : dotnet-apphost-pack-5.0-5.0.1-1.x86_64                                       2/10 
  Verifying  : aspnetcore-targeting-pack-5.0-5.0.0-1.x86_64                                 3/10 
  Verifying  : netstandard-targeting-pack-2.1-2.1.0-1.x86_64                                4/10 
  Verifying  : dotnet-runtime-5.0-5.0.1-1.x86_64                                            5/10 
  Verifying  : aspnetcore-runtime-5.0-5.0.1-1.x86_64                                        6/10 
  Verifying  : dotnet-targeting-pack-5.0-5.0.0-1.x86_64                                     7/10 
  Verifying  : dotnet-sdk-5.0-5.0.101-1.x86_64                                              8/10 
  Verifying  : dotnet-runtime-deps-5.0-5.0.1-1.x86_64                                       9/10 
  Verifying  : dotnet-host-5.0.1-1.x86_64                                                  10/10 

Installed:
  dotnet-sdk-5.0.x86_64 0:5.0.101-1                                                              

Dependency Installed:
  aspnetcore-runtime-5.0.x86_64 0:5.0.1-1         aspnetcore-targeting-pack-5.0.x86_64 0:5.0.0-1
  dotnet-apphost-pack-5.0.x86_64 0:5.0.1-1        dotnet-host.x86_64 0:5.0.1-1                  
  dotnet-hostfxr-5.0.x86_64 0:5.0.1-1             dotnet-runtime-5.0.x86_64 0:5.0.1-1           
  dotnet-runtime-deps-5.0.x86_64 0:5.0.1-1        dotnet-targeting-pack-5.0.x86_64 0:5.0.0-1    
  netstandard-targeting-pack-2.1.x86_64 0:2.1.0-1

Complete!

.NET 5がインストールされました。

CloudShell
$ dotnet --version
5.0.101

ビルドしてWindows向けの実行ファイルをダウンロードする

Console アプリを作成して実行してみる。

CloudShell
$ mkdir consoleapp
$ cd consoleapp
$ dotnet new console
$ dotnet run
Hello World!

Program.csを開き、

CloudShell
$ ls
bin  obj  Program.cs  sample.csproj
$ vi Program.cs 

まぁ、よくある修正をして保存後、

Program.cs
using System;

namespace sample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello CloudShell");
        }
    }
}

ビルドして実行すると反映されましたね。

CloudShell
$ dotnet run
Hello CloudShell

Windows 向けにビルドします。

CloudShell
$ dotnet publish -r win-x86 -c Release -p:PublishSingleFile=true
Microsoft (R) Build Engine version 16.8.0+126527ff1 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  sample -> /home/cloudshell-user/sample/bin/Release/net5.0/win-x86/sample.dll
  sample -> /home/cloudshell-user/sample/bin/Release/net5.0/win-x86/publish/

出来上がったファイルをtarで固めてダウンロードします。

CloudShell
$ cd /home/cloudshell-user/sample/bin/Release/net5.0/win-x86/
$ tar cvfz ~/app.tar.gz publish/
publish/
publish/sample.exe
publish/mscordaccore.dll
publish/clrjit.dll
publish/clrcompression.dll
publish/sample.pdb
publish/coreclr.dll

Actions → Download fileでファイルをダウンロードできます。
image.png
image.png

Windowsで実行してみる。
image.png
動きましたね!

まとめ

  • AWS CloudShell上でも入れれば.NET動くよ
  • .NET のクロスコンパイラでWindows、Macの実行ファイルを作成できるよ
  • ビルド方法によっては.NET ランタイムがインストールされていなくても動作するよ

CloudShellでは永続化されるのはHomeディレクトリ配下の1GBのファイルだけで、他は起動するたびに失われるので最初からインストールされているランタイムを使ったほうが良いですよね。PowerShell Coreも入っていますし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】Task

Task

単に「仕事」を意味する動作を指す。

同期型メソッドと非同期型メソッド

まず、同期型メソッドは1から順にTaskを実行するメソッド。
それに対して、非同期型は順序問わず、Taskを実行するメソッドを言う。

★非同期型メソッドのキーワード

async修飾子
 メソッド内でawait演算子を利用するための修飾子。

sample.cs
private async void Click_Action(object sender, RoutedEventArgs e)
{
    // 処理を記述   
}

await演算子
async修飾子が付いているメソッド内で1以上記述できる。
逆を言えばasync修飾子内に1つ以上のawait演算子が必要となる。

Sample.cs
private async void Click_Action(object sender, RoutedEventArgs e)
{
    this.botton.IsEnabled = flase;
    await MouseActionMethod(); // 何かしらの処理を呼び出し
    this.botton.IsEnabled = true;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ASP.NET Core+NUnitでインテグレーションテスト

はじめに

ASP.NET CoreではコントローラーやRazorPagesに対するインテグレーションテストを想定して、インメモリでテスト用WebサーバーをホストするためのWebApplicationFactoryクラスがあらかじめ用意されています。このクラスの利用方法はASP.NET Core MVC アプリのテストにまとめられていますが、対象がXUnitになっているためNUnitの場合このままでは利用できません。
この記事では、ASP.NET Core+NUnitを利用したインテグレーションテストの開始方法を説明します。

テスト用プロジェクトの作成と必要なパッケージのインストール

dotnetコマンドで作られるテスト対象のWebApiプロジェクトと、NUnitを利用したテストプロジェクトを追加していきます。
テストプロジェクトはテスト対象のWebApiプロジェクトに対する参照と、WebApplicationFactoryクラスを利用するためにMicrosoft.AspNetCore.Mvc.Testingパッケージを追加します。

dotnet new webapi -o ASPNETCoreNUnitItSample/WebApi
dotnet new nunit -o ASPNETCoreNUnitItSample/WebApi.Tests

cd ASPNETCoreNUnitItSample
dotnet new sln
dotnet sln add ./WebApi/WebApi.csproj
dotnet sln add ./WebApi.Tests/WebApi.Tests.csproj

dotnet add ./WebApi.Tests/WebApi.Tests.csproj reference --interactive ./WebApi/WebApi.csproj
dotnet add ./WebApi.Tests/WebApi.Tests.csproj package Microsoft.AspNetCore.Mvc.Testing

Visual Studioでソリューションを開くと、次のような構成のソリューションが作成されます。
image.png

インテグレーション用テスト用Webサーバーの起動コードを追加

まずはプロジェクトファイルの<Project Sdk="Microsoft.NET.Sdk"><Project Sdk="Microsoft.NET.Sdk.Web">に変更します。

WebApiTests.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.1" />
    <PackageReference Include="NUnit" Version="3.12.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\WebApi\WebApi.csproj" />
  </ItemGroup>

</Project>

続いて、テスト用Webサーバーの起動クラスを追加します。

ApiWebApplicationFactory.cs
using Microsoft.AspNetCore.Mvc.Testing;

namespace WebApi.Tests
{
    public class ApiWebApplicationFactory: WebApplicationFactory<WebApi.Startup>
    {
    }
}

テストコードの追加

XUnitの例では、IClassFixture<T>を継承することでWebApplicationFactoryのインスタンスを取得していましたが、NUnitではIClassFixtureは使えないので、OneTimeSetup属性で指定したメソッドでApiWebApplicationFactoryを作成し、HttpClientを取り出してこれを利用します。

WeatherForecastControllerTest.cs
using System.Net.Http;
using System.Threading.Tasks;
using NuGet.Frameworks;
using NUnit.Framework;

namespace WebApi.Tests.Controllers
{
    [TestFixture]
    class WeatherForecastControllerTest
    {
        private HttpClient _httpClient;

        [OneTimeSetUp]
        public void OneTimeSetup()
        {
            var factory = new ApiWebApplicationFactory();
            _httpClient = factory.CreateClient();
        }

        [Test]
        public async Task GetTest()
        {
            var response = await _httpClient.GetAsync("/WeatherForecast");
            Assert.IsTrue(response.IsSuccessStatusCode);
        }
    }
}

まとめ

  • NUnitでインテグレーションテストを実施する場合は、自分でMicrosoft.AspNetCore.Mvc.Testingパッケージを追加する
  • Factoryの生成はOneTimeSetupで指定したメソッドで実施する

実際にインテグレーションテストを実施する場合は、ConfigureWebHostメソッドをオーバーライドしてデータベースの参照先をRDBからメモリーデータベースにしたり、外部サービスをモックに差し替えるなどいくつか追加で実施する必要がありますが、実施方法はXUnitと変わりがありませんのでASP.NET Core MVC アプリのテストのASP.NET Core アプリを機能テストするを参照してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でAtCoderデビューのための準備

まずは門を叩いて入門、
処理を高速化することやエラー処理のことはここでは考えないでおきます。

標準入力

// 文字列の入力
string s = Console.ReadLine();

// 整数の入力
long n = long.Parse(Console.ReadLine());

// 文字列配列の入力
string[] inputStrArray = Console.ReadLine().Split(' ');

// 整数配列の入力
long[] inputLongArray = Console.ReadLine().Split(' ').Select(i => long.Parse(i)).ToArray();

配列の初期化

// 配列を生成する
var array = new int[] {0, 2, 4, 6};

// [0,1...] の配列を生成する
var array = Enumerable.Range(0, 5).ToArray(); // {0, 1, 2, 3, 4}

// 初期値が全て同じ配列を生成する
var array = Enumerable.Repeat(-1, 5).ToArray(); // { -1, -1, -1, -1, -1}

参考

競技プログラミングのための C# (4.0 以降) の Tips 詰め合わせ
https://emkcsharp.hatenablog.com/entry/2013/Advent

初心者がC#でAtCoderデビューするためのVSProjectテンプレート
https://qiita.com/sekikatsu/items/93c41c6c937ed1dfcf23

AtCoderで使えそうなC# 7.0~8.0の新機能
https://www.terry-u16.net/entry/csharp-7-8-new-features-for-atcoder

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

有理数型を実装する

C#で有理数型を実装してみた例です。

有理数で実現したいこと

有理数型として下記の機能を作りたいと思います。

  • 加算
  • 減算
  • 乗算
  • 除算
  • 比較
  • 浮動小数点数への変換

型の定義

long の既約分数として有理数を表現することにします。符号はどちらでも良いですが、分子に集約することにします。

既約分数は分子と分母を最大公約数で割ると良いので、最大公約数をユークリッドの互除法で求めてしまえば良いです。

Fraction.cs
/// <summary>有理数を既約分数で表す</summary>
public readonly struct Fraction : IEquatable<Fraction>, IComparable<Fraction>
{
    /// <summary>分子</summary>
    public long Numerator { get; }
    /// <summary>分母</summary>
    public long Denominator { get; }


    public Fraction(long 分子, long 分母)
    {
        var negative = (分子 ^ 分母) < 0;
        分子 = Math.Abs(分子);
        分母 = Math.Abs(分母);
        var gcd = Gcd(分母, 分子);
        _numerator = 分子 / gcd;
        if (negative)
            _numerator = -_numerator;
        _denominator = 分母 / gcd - 1;
    }

    /// <summary>
    /// 最大公約数をユークリッドの互除法で求める
    /// </summary>
    public static long Gcd(long a, long b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b));
}

四則演算の定義

小学校の算数で習う分数の計算をそのまま実装します。
C# は演算子オーバーロードを定義できるので、素直に実装します。

加算、減算した結果の分母は計算するそれぞれの分母の最小公倍数になります。
x, y の最小公倍数は $ \frac{x y}{最大公約数} $ と表せます。

Fraction.cs
public static Fraction operator -(Fraction x) => new Fraction(-x.Numerator, x.Denominator);
public static Fraction operator +(Fraction x, Fraction y)
{
    var gcd = Gcd(x.Denominator, y.Denominator);
    var lcm = x.Denominator / gcd * y.Denominator;
    return new Fraction((x.Numerator * y.Denominator + y.Numerator * x.Denominator) / gcd, lcm);
}
public static Fraction operator -(Fraction x, Fraction y)
{
    var gcd = Gcd(x.Denominator, y.Denominator);
    var lcm = x.Denominator / gcd * y.Denominator;
    return new Fraction((x.Numerator * y.Denominator - y.Numerator * x.Denominator) / gcd, lcm);
}
public static Fraction operator *(Fraction x, Fraction y) => new Fraction(x.Numerator * y.Numerator, x.Denominator * y.Denominator);
public static Fraction operator /(Fraction x, Fraction y) => new Fraction(x.Numerator * y.Denominator, x.Denominator * y.Numerator);

比較の定義

\frac{a}{b} < \frac{c}{d}

という不等式は両辺に $ bd $ を掛けて、

ad < bd

と変形できるので、long型の演算で定義できます。

Fraction.cs
public int CompareTo(Fraction other) => (this.Numerator * other.Denominator).CompareTo(other.Numerator * this.Denominator);
public static bool operator ==(Fraction x, Fraction y) => x.Equals(y);
public static bool operator !=(Fraction x, Fraction y) => !x.Equals(y);
public static bool operator >=(Fraction x, Fraction y) => x.CompareTo(y) >= 0;
public static bool operator <=(Fraction x, Fraction y) => x.CompareTo(y) <= 0;
public static bool operator >(Fraction x, Fraction y) => x.CompareTo(y) > 0;
public static bool operator <(Fraction x, Fraction y) => x.CompareTo(y) < 0;

変換

double への変換やlongからの暗黙的な変換も定義しておくと使いやすいでしょう。
longからの変換は分母を1にするだけです。

Fraction.cs
public double ToDouble() => (double)Numerator / Denominator;
public static implicit operator Fraction(long x) => new Fraction(x, 1);

デフォルト値の問題

しかし、このような実装ではデフォルト値が 0/0 となってしまいます。

そのため new Fraction(2,3) * default(Fraction) がゼロ除算を引き起こしてしまいます。

  • new Fraction(2,3) + default(Fraction) == new new Fraction(2,3)
  • new Fraction(2,3) + default(Fraction) == new new Fraction(0,1)

を満たすようにしたいところです。

これは、型の内部表現では分母の値を-1すると解決します。
つまり、分母が1のときは内部では0を保持、分母が2のときは内部では1を保持、という具合です。

こうすることで、default(Fraction) == new new Fraction(0,1) となり上記のデフォルト値での演算も期待通りになります。

Fraction.cs
public readonly struct Fraction
{
    /// <summary>分子</summary>
    private readonly long _numerator;
    /// <summary>分子</summary>
    public long Numerator => _numerator;
    /// <summary>分母 - 1 (default を 0/0 ではなく 0/1 にしたい)</summary>
    private readonly long _denominator;
    /// <summary>分母</summary>
    public long Denominator => _denominator + 1;

    public Fraction(long 分子, long 分母)
    {
        var negative = (分子 ^ 分母) < 0;
        分子 = Math.Abs(分子);
        分母 = Math.Abs(分母);
        if (分子 == 0)
        {
            _numerator = 0;
            _denominator = 0;
        }
        else
        {
            var gcd = Gcd(分母, 分子);
            _numerator = 分子 / gcd;
            if (negative)
                _numerator = -_numerator;
            _denominator = 分母 / gcd - 1;
        }
    }
}

完成

上記の検討から有理数型を作ることができました。

Fraction.cs
/// <summary>有理数を既約分数で表す</summary>
public readonly struct Fraction : IEquatable<Fraction>, IComparable<Fraction>
{
    /// <summary>分子</summary>
    private readonly long _numerator;
    /// <summary>分子</summary>
    public long Numerator => _numerator;
    /// <summary>分母 - 1 (default を 0/0 ではなく 0/1 にしたい)</summary>
    private readonly long _denominator;
    /// <summary>分母</summary>
    public long Denominator => _denominator + 1;

    public Fraction(long 分子, long 分母)
    {
        var negative = (分子 ^ 分母) < 0;
        分子 = Math.Abs(分子);
        分母 = Math.Abs(分母);
        if (分子 == 0)
        {
            _numerator = 0;
            _denominator = 0;
        }
        else
        {
            var gcd = Gcd(分母, 分子);
            _numerator = 分子 / gcd;
            if (negative)
                _numerator = -_numerator;
            _denominator = 分母 / gcd - 1;
        }
    }

    /// <summary>
    /// 最大公約数をユークリッドの互除法で求める
    /// </summary>
    public static long Gcd(long a, long b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b));
    public override string ToString() => $"{Numerator}/{Denominator}";
    public override bool Equals(object obj) => obj is Fraction f && Equals(f);
    public bool Equals(Fraction other) => this._numerator == other._numerator && this._denominator == other._denominator;
    public override int GetHashCode() => HashCode.Combine(_numerator, _denominator);

    public static implicit operator Fraction(long x) => new Fraction(x, 1);

    public static Fraction operator -(Fraction x) => new Fraction(-x.Numerator, x.Denominator);
    public static Fraction operator +(Fraction x, Fraction y)
    {
        var gcd = Gcd(x.Denominator, y.Denominator);
        var lcm = x.Denominator / gcd * y.Denominator;
        return new Fraction((x.Numerator * y.Denominator + y.Numerator * x.Denominator) / gcd, lcm);
    }
    public static Fraction operator -(Fraction x, Fraction y)
    {
        var gcd = Gcd(x.Denominator, y.Denominator);
        var lcm = x.Denominator / gcd * y.Denominator;
        return new Fraction((x.Numerator * y.Denominator - y.Numerator * x.Denominator) / gcd, lcm);
    }
    public static Fraction operator *(Fraction x, Fraction y) => new Fraction(x.Numerator * y.Numerator, x.Denominator * y.Denominator);
    public static Fraction operator /(Fraction x, Fraction y) => new Fraction(x.Numerator * y.Denominator, x.Denominator * y.Numerator);

    public int CompareTo(Fraction other) => (this.Numerator * other.Denominator).CompareTo(other.Numerator * this.Denominator);
    public static bool operator ==(Fraction x, Fraction y) => x.Equals(y);
    public static bool operator !=(Fraction x, Fraction y) => !x.Equals(y);
    public static bool operator >=(Fraction x, Fraction y) => x.CompareTo(y) >= 0;
    public static bool operator <=(Fraction x, Fraction y) => x.CompareTo(y) <= 0;
    public static bool operator >(Fraction x, Fraction y) => x.CompareTo(y) > 0;
    public static bool operator <(Fraction x, Fraction y) => x.CompareTo(y) < 0;

    public Fraction Inverse() => new Fraction(Denominator, Numerator);
    public double ToDouble() => (double)Numerator / Denominator;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む