20200623のC#に関する記事は7件です。

#programming #csharp クラス内に定義したクラスの正式名(qualified name)

「+」で連結されるっぽい。

using System;

namespace qualified_name
{
    class Program
    {
        public class SubClass
        {
            public SubClass()
            {
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine(typeof(SubClass).ToString());
        }
    }
}
>qualified_name.exe
qualified_name.Program+SubClass

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

Azure Functions Core Tools v3.xを使ってローカル関数プロジェクト開発をする方法

はじめに

Azure Functions のローカル関数プロジェクトを利用した開発方法については、公式サイトの「Azure Functions Core Tools の操作」で詳しく紹介されています。

このページの通り進めることで Azure Functions Core Tools のバージョンが v2.x ではうまく動作させることが可能となっていますが、v3.x ではうまく動作しません。
// Node.js 13.x 以降を利用している場合に限る

また、Azure Functions は F# に対応しているにも関わらず、Azure Functions Core Toolsを使って F# のローカル関数プロジェクトを作成することができません

そこで今回は (1) Azure Functions Core Toolsv3.x を利用 しつつ、(2) F# でローカル関数プロジェクト開発するための方法 について紹介していきたいと思います。

前提条件

筆者の動作環境は以下のようになっています。

名称 バージョン
Windows 10 64bit 1903
Visual Studio Code 1.46.1
.NET Core 3.1.301
node 14.1.0
npm 6.14.5

バージョンが更新されることで、今回の対応が不要になる可能性が高まると思います。

Azure Functions Core Tools v3.x の導入

以下のコマンドを利用して Azure Functions Core Tools v3.x をインストールします。

powershell
npm install -g azure-functions-core-tools@3 --unsafe-perm true

すると以下のようにダウンロードが完了していないにも関わらず、インストール処理が終了してしまうと思います。

# <中略>
attempting to GET "https://functionscdn.azureedge.net/public/3.0.2534/Azure.Functions.Cli.win-x64.3.0.2534.zip"
[------------------] Downloading Azure Functions Core Tools+ azure-functions-core-tools@3.0.2534
updated 6 packages in 1.289s

そこで以下のコマンドを実行し、azure-functions-core-tools に unzipper@0.10.7 をインストールします。

powershell
cd $env:AppData/npm/node_modules/azure-functions-core-tools
npm install unzipper@0.10.7
node .\lib\install.js

参考 Issue: Any 'func' command does not work #1804

すると無事 Azure Functions Core Tools v3.x がインストールされます。
これでめでたく func コマンドが利用できるようになりました。

F# でローカル関数プロジェクトを作成する

通常、func init コマンドを利用することでローカル関数プロジェクトを作成することができます。
2020/06/23 現在、C# / Java / JavaScript / TypeScript / PowerShell / Python の6言語についてはオプションを指定することでプロジェクトを即座に作成することができます。

そう、F# を除くすべての言語が対応しているのです!!
// Microsoft、そういうトコやぞ

では F# はどうすればよいのかというと、仕方がないので C# のプロジェクトを修正するしかありません。

まずは以下のコマンドでローカル関数プロジェクトを作成します。

powershell
func init SampleFuncProj

すると以下のような選択肢が現れるので、dotnet を選択します。
image.png

実行が完了するとローカル関数プロジェクトができあがります。
これを VSCode で起動します。

powershell
code ./SampleFuncProj

VSCode 上では次のような構成になっていると思います。
image.png

次に SampleFuncProj .csproj を SampleFuncProj .fsproj にリネームします。
そして SampleFuncProj.fsproj 内の以下の箇所を書き換えます。

SampleFuncProj.fsproj
<!-- <None Update="host.json"> --> 10行目あたりのコード
     <None Include="host.json">

参考 Issue: Host.json file in missing #1963

修正後は以下のようになっていると思います。
image.png

これで F# 用のローカル関数プロジェクトが完成しました!

おまけ:F# のローカル関数プロジェクトで Hello, World してみる

fsproj を Visual Studio で開くことで C# と同じように開発できるのですが、今回は VSCode を利用しての開発方法について紹介しておきます。
hello.fs というファイルを作成し、SampleFuncProj.fsproj を以下のように修正しています。
image.png

※ファイルの追加については ionide を利用することで、fsproj に対象ファイルの情報が自動で反映されるのでそちらを利用する方が良いと思います。

次に hello.fs を以下のようにします。

hello.fs
module Hello

open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http

[<FunctionName("hello")>]
let run ([<HttpTrigger(AuthorizationLevel.Function, "post", "get", Route = null)>] req: HttpRequest) =
    OkObjectResult "Hello, World" :> ActionResult

最後に以下のコマンドを実行することで、Azure Functions をローカル実行することができます。

powershell
func start --build

以下のエンドポイントにアクセスすることで実行することができます。
image.png
image.png

おまけのおまけ

余談ですが VSCode に Azure Functions の拡張機能を入れているとローカル関数プロジェクトを開いたときに以下のようなメッセージが表示されます。
image.png

これで Yes を選択してあげると、F5 でデバッグ実行が可能となります。

もちろんブレークポイントを設定してステップ実行をすることも可能です。

もし VSCode で Azure Functions の開発をする際には是非利用してみてください。

【参考画像】
image.png

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

Azure Functions Core Tools v3.x を使ってローカル関数プロジェクト開発をする方法

はじめに

Azure Functions のローカル関数プロジェクトを利用した開発方法については、公式サイトの「Azure Functions Core Tools の操作」で詳しく紹介されています。

このページの通り進めることで Azure Functions Core Tools のバージョンが v2.x ではうまく動作させることが可能となっていますが、v3.x ではうまく動作しません。
// Node.js 13.x 以降を利用している場合に限る

また、Azure Functions は F# に対応しているにも関わらず、Azure Functions Core Toolsを使って F# のローカル関数プロジェクトを作成することができません

そこで今回は (1) Azure Functions Core Toolsv3.x を利用 しつつ、(2) F# でローカル関数プロジェクト開発するための方法 について紹介していきたいと思います。

前提条件

筆者の動作環境は以下のようになっています。

名称 バージョン
Windows 10 64bit 1903
Visual Studio Code 1.46.1
.NET Core 3.1.301
node 14.1.0
npm 6.14.5

バージョンが更新されることで、今回の対応が不要になる可能性が高まると思います。

Azure Functions Core Tools v3.x の導入

以下のコマンドを利用して Azure Functions Core Tools v3.x をインストールします。

powershell
npm install -g azure-functions-core-tools@3 --unsafe-perm true

すると以下のようにダウンロードが完了していないにも関わらず、インストール処理が終了してしまうと思います。

# <中略>
attempting to GET "https://functionscdn.azureedge.net/public/3.0.2534/Azure.Functions.Cli.win-x64.3.0.2534.zip"
[------------------] Downloading Azure Functions Core Tools+ azure-functions-core-tools@3.0.2534
updated 6 packages in 1.289s

そこで以下のコマンドを実行し、azure-functions-core-tools に unzipper@0.10.7 をインストールします。

powershell
cd $env:AppData/npm/node_modules/azure-functions-core-tools
npm install unzipper@0.10.7
node .\lib\install.js

参考 Issue: Any 'func' command does not work #1804

すると無事 Azure Functions Core Tools v3.x がインストールされます。
これでめでたく func コマンドが利用できるようになりました。

F# でローカル関数プロジェクトを作成する

通常、func init コマンドを利用することでローカル関数プロジェクトを作成することができます。
2020/06/23 現在、C# / Java / JavaScript / TypeScript / PowerShell / Python の6言語についてはオプションを指定することでプロジェクトを即座に作成することができます。

そう、F# を除くすべての言語が対応しているのです!!
// Microsoft、そういうトコやぞ

では F# はどうすればよいのかというと、仕方がないので C# のプロジェクトを修正するしかありません。

まずは以下のコマンドでローカル関数プロジェクトを作成します。

powershell
func init SampleFuncProj

すると以下のような選択肢が現れるので、dotnet を選択します。
image.png

実行が完了するとローカル関数プロジェクトができあがります。
これを VSCode で起動します。

powershell
code ./SampleFuncProj

VSCode 上では次のような構成になっていると思います。
image.png

次に SampleFuncProj .csproj を SampleFuncProj .fsproj にリネームします。
そして SampleFuncProj.fsproj 内の以下の箇所を書き換えます。

SampleFuncProj.fsproj
<!-- <None Update="host.json"> --> 10行目あたりのコード
     <None Include="host.json">

参考 Issue: Host.json file in missing #1963

修正後は以下のようになっていると思います。
image.png

これで F# 用のローカル関数プロジェクトが完成しました!

おまけ:F# のローカル関数プロジェクトで Hello, World してみる

fsproj を Visual Studio で開くことで C# と同じように開発できるのですが、今回は VSCode を利用しての開発方法について紹介しておきます。
hello.fs というファイルを作成し、SampleFuncProj.fsproj を以下のように修正しています。
image.png

※ファイルの追加については ionide を利用することで、fsproj に対象ファイルの情報が自動で反映されるのでそちらを利用する方が良いと思います。

次に hello.fs を以下のようにします。

hello.fs
module Hello

open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http

[<FunctionName("hello")>]
let run ([<HttpTrigger(AuthorizationLevel.Function, "post", "get", Route = null)>] req: HttpRequest) =
    OkObjectResult "Hello, World" :> ActionResult

最後に以下のコマンドを実行することで、Azure Functions をローカル実行することができます。

powershell
func start --build

以下のエンドポイントにアクセスすることで実行することができます。
image.png
image.png

おまけのおまけ

余談ですが VSCode に Azure Functions の拡張機能を入れているとローカル関数プロジェクトを開いたときに以下のようなメッセージが表示されます。
image.png

これで Yes を選択してあげると、F5 でデバッグ実行が可能となります。

もちろんブレークポイントを設定してステップ実行をすることも可能です。

もし VSCode で Azure Functions の開発をする際には是非利用してみてください。

【参考画像】
image.png

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

Visual Studio 2019 コードクリーンアップの動作をC#サンプル付きで紹介する

Visual Studio 2019にはコードクリーンアップという機能が付いています。これはあらかじめ設定しておいたルールに従って、ソースコードをまとめて綺麗にしてくれるというものです。
(従来からあった「フォーマット」機能とは異なり、変数宣言にvarを使うか使わないか、自分自身のメソッド呼び出しにthisを付ける/付けないなど、コードの内容部分まで含めて綺麗にしてくれます)

WS000831.PNG

WS000833.PNG

これはとても便利な機能なのですが、惜しいことにどのルールが具体的にどんな処理をしてくれるのかが分かりにくいという欠点があります。
ルールの設定画面では処理の実例が表示されず、各ルールについての説明なども特にないためです。

そこでこの記事では、各ルールがどのような処理を行ってくれるのかを、C#のコードサンプルと合わせて紹介します。
また、実際に行われるクリーンアップ処理の内容は、Visual Studioのコードスタイル設定や .editorconfig の設定によって変わります。そのため、「Visual Studioや .editorconfig のどの設定がクリーンアップ処理に影響するか」も併せて記載します。

1. 'this.' 修飾の基本設定を適用します

自分自身が持つプロパティ、メソッド呼び出しの前に this. を付けるかどうかを統一します。

クリーンアップ前
public class Class1
{
    private string FirstName { get; set; }
    private string FamilyName { get; set; }

    public virtual string GetNameWithSuffix(string suffix)
    {
        return this.FirstName + suffix;
    }

    public override string ToString()
    {
        return this.GetNameWithSuffix("さん");
    }
}
クリーンアップ後
public class Class1
{
    private string FirstName { get; set; }
    private string FamilyName { get; set; }

    public virtual string GetNameWithSuffix(string suffix)
    {
        return FirstName + suffix; /* this. なしに統一 */
    }

    public override string ToString()
    {
        return GetNameWithSuffix("さん"); /* this. なしに統一 */
    }
}

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 全般 > 'this' の優先 > ○○ アクセスを 'this' で修飾してください

WS000818.PNG

editorconfigで対応する設定:
dotnet_style_qualification_for_***

# this. と Me. の設定
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent

参考ページ:
"This." 修飾子 と "Me." 修飾子 (EditorConfig での .NET の言語規則 - Visual Studio)

2. using を並べ替える

usingを適切な順番に並べ替えます。

クリーンアップ前
using System;
using Newtonsoft.Json;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Converters;
using System.IO;
using Semver;
クリーンアップ後
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Semver;
using System;
using System.IO;
using System.Linq;
using System.Text;

なお、Visual Studio 2019の標準では上記のようにアルファベット順に並べ替えますが、設定を変更することでSystemを先頭に並び替えたり、グループごと(名前空間の第1階層ごと)に空行を空けて並べたりすることも可能になります。

using System;
using System.IO;
using System.Linq;
using System.Text;

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

using Semver;

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > 詳細 > ディレクティブを使用する

WS000819.PNG

editorconfigで対応する設定:
dotnet_separate_import_directive_groups, dotnet_sort_system_directives_first

# using の整理
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset

参考ページ:
using ディレクティブの整理 (EditorConfig での .NET の書式規則 - Visual Studio)

3. 不要な using の削除

ファイル内で使用していないusing句を削除します。

クリーンアップ前
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Semver;
using System;
using System.IO;
using System.Linq;
using System.Text;

namespace CodeCleanupDemo
{
    public class TestClass1
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            File.WriteAllText("world.log", "Hello World!");
        }
    }
}
クリーンアップ後
using System;
using System.IO;

namespace CodeCleanupDemo
{
    public class TestClass1
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            File.WriteAllText("world.log", "Hello World!");
        }
    }
}

Visual Studio 2019で対応する設定:
なし

editorconfigで対応する設定:
なし

4. 暗黙的/明示的な型の基本設定を適用します

ローカル変数の宣言時に var を使うかどうかを統一します。

クリーンアップ前
var num1 = 100;
var str1 = "foo";
クリーンアップ後
int num1 = 100;
string str1 = "foo";

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 全般 > 'var' を優先

WS000825.PNG

editorconfigで対応する設定:
csharp_style_var_***

# var を優先
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent

参考ページ:
暗黙的な型と明示的な型 (EditorConfig での .NET の言語規則 - Visual Studio)

5. 未使用の変数を削除する

使われていないローカル変数を削除します。

クリーンアップ前
public static void Main(string[] args)
{
    var num = 100;

    Console.WriteLine("Hello World!");
}
クリーンアップ後
public static void Main(string[] args)
{
    Console.WriteLine("Hello World!");
}

Visual Studio 2019で対応する設定:
なし

editorconfigで対応する設定:
なし

参考ページ:
コンパイラの警告 (レベル 3) CS0219

6. 不要なキャストを削除する

不要と考えられる型キャストを削除します。

クリーンアップ前
public static void Resize(Rectangle baseRect, int requiredWidth, int requiredHeight)
{
    var left = (int)baseRect.X;

    var scaleRateByWidth = (double)baseRect.Width / (double)requiredWidth;
    var scaleRateByHeight = (double)baseRect.Height / (double)requiredHeight;
クリーンアップ後
public static void Resize(Rectangle baseRect, int requiredWidth, int requiredHeight)
{
    var left = baseRect.X;  // Rectangle.Xはもともとint型のため、int型へのキャストは不要

    var scaleRateByWidth = baseRect.Width / (double)requiredWidth; // requiredWidthだけdouble型にキャストしても結果は同じ
    var scaleRateByHeight = baseRect.Height / (double)requiredHeight; // 同上

Visual Studio 2019で対応する設定:
なし

editorconfigで対応する設定:
なし

参考ページ:
不要なキャストを削除する (共通のクイックアクション - Visual Studio)

7. インラインの 'out' 変数の基本設定を適用します

関数呼び出し時にout引数を指定していて、かつout引数で使う変数をそれよりも前に宣言している場合、C# 7.0から導入された書き方(out変数)に変更します。

クリーンアップ前
public void TestMethod1()
{
    /* C# 6.0 / Visual Studio 2015以前では、out引数に使うための変数を、事前に宣言しておく必要があった */
    int a;    
    TestMethod2(out a);

    var a3x = a * 3;
} 

public void TestMethod2(out int outParam1)
{
    outParam1 = 100;
}
クリーンアップ後
public void TestMethod1()
{
    /* C# 7.0 / Visual Studio 2017以降ではこのように1行で書ける */
    TestMethod2(out int a);    

    var a3x = a * 3;
}

public void TestMethod2(out int outParam1)
{
    outParam1 = 100;
}

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 変数の優先順位 > インライン変数宣言を優先する

WS000826.PNG

editorconfigで対応する設定:
csharp_style_inlined_variable_declaration

# 式レベルの設定
csharp_style_inlined_variable_declaration = true:suggestion

参考ページ:
インライン変数宣言 (EditorConfig での .NET の言語規則 - Visual Studio)

8. アクセシビリティ修飾子を追加します

アクセシビリティ (public, protected, privateなど) の記載がされていないプロパティやメソッドについて、アクセシビリティを記載します。

クリーンアップ前
double Rate { get; set; }  // アクセシビリティは指定していないがprivate扱い

static void Resize(Rectangle baseRect, int requiredWidth, int requiredHeight)  // 同上
{
}
クリーンアップ後
private double Rate { get; set; }

private static void Resize(Rectangle baseRect, int requiredWidth, int requiredHeight)
{
}

Visual Studio 2019で対応する設定:
なし

editorconfigで対応する設定:
なし

9. アクセシビリティ修飾子を並べ替える

アクセシビリティ (public, protected, privateなど) の記載順を、標準の並び順に従って並び替えます。
また、static修飾子やvirtual修飾子などよりも前に(先頭に)来るようにします。

クリーンアップ前
static private void Resize(Rectangle baseRect, int requiredWidth, int requiredHeight)
{
}

internal protected void Resize(Rectangle baseRect)
{
}
クリーンアップ後
private static void Resize(Rectangle baseRect, int requiredWidth, int requiredHeight)
{
}

protected internal void Resize(Rectangle baseRect)
{
}

Visual Studio 2019で対応する設定:
なし

editorconfigで対応する設定:
なし

10. 可能な場合、privateフィールドを読み取り専用にする

初期化以外の箇所で代入を行っていないprivateフィールド(メンバ変数)があれば、それをreadonlyにします。

※readonlyになるのはフィールドだけで、プロパティは読み取り専用にならないことに注意してください。

クリーンアップ前
public class TestClass1
{
    private string _world = "World";

    public virtual void HelloWorld()
    {
        Console.WriteLine($"Hello, {_world}");
    }
}
クリーンアップ後
public class TestClass1
{
    /* 初期化以外で代入(再設定)を行っていないため、readonlyになる */
    private readonly string _world = "World"; 

    public virtual void HelloWorld()
    {
        Console.WriteLine($"Hello, {_world}");
    }
}

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 修飾子設定 > readonly フィールドを優先する

WS000827.PNG

editorconfigで対応する設定:
dotnet_style_readonly_field

# フィールド設定
dotnet_style_readonly_field = true:suggestion

参考ページ:
dotnet_style_readonly_field (EditorConfig での .NET の言語規則 - Visual Studio)

11. 言語/フレームワークの型の基本設定を適用します。

標準の組み込み型名について、言語キーワード(int, long, stringなど)で記述するか、フレームワークの型名 (Int32, Int64, System.Stringなど) で記述するかを統一します。

クリーンアップ前
Int32 num1 = 100;
System.String str1 = "foo";

var num2 = Int64.MaxValue;
クリーンアップ後
int num1 = 100;
string str1 = "foo";

var num2 = long.MaxValue;

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 定義済みの型の設定

WS000829.PNG

editorconfigで対応する設定:
dotnet_style_predefined_type_for_***

# 言語キーワードと BCL の種類の設定
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent

参考ページ:
型参照のためのフレームワーク型名の代わりの言語キーワード (EditorConfig での .NET の言語規則 - Visual Studio)

12. 単一行のコントロール ステートメントに対する波かっこの追加/削除を行います

if文やusing文などの本体を1行だけで書けるような場合に、波かっこをつけて複数行で記述するか、1行で記述するかを統一します。

クリーンアップ前
public virtual string GetNameWithSuffix(string name, string suffix)
{
    if (name == null) return null;
クリーンアップ後
public virtual string GetNameWithSuffix(string name, string suffix)
{
    if (name == null)
    {
        return null;
    }

なお、この設定には「はい」「いいえ」の他に「複数行の場合」という設定があり、これを選択すると「本体が1行に収まるときは1行で書く、複数行にわたるときは波かっこをつける」という動作となります。

クリーンアップ前
public virtual string GetNameWithSuffix(string name, string suffix)
{
    if (name == null) return null;

    if (suffix != null)
        return string.Format("こんにちは、{0} {1}",
                             name,
                             suffix);
クリーンアップ後
public virtual string GetNameWithSuffix(string name, string suffix)
{
    /* 本体が1行なのでそのまま */
    if (name == null) return null;

    /* 本体が複数行なので波かっこが付く */
    if (suffix != null)
    {
        return string.Format("こんにちは、{0} {1}",
                        name,
                        suffix);
    }

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > コード ブロックの優先順位 > 波かっこを優先します

WS000830.PNG

editorconfigで対応する設定:
csharp_prefer_braces

# コード ブロックの設定
csharp_prefer_braces = true:silent

参考ページ:
コード ブロックの基本設定 (EditorConfig での .NET の言語規則 - Visual Studio)

13. オブジェクト/コレクションの初期化の基本設定を適用します

オブジェクトやコレクションを初期化する時の書き方を統一します。

クリーンアップ前
public class Human
{
    public virtual string Name { get; set; }
    public virtual int Height { get; set; }

    public static Human Create()
    {
        var human1 = new Human();
        human1.Name = "Taro";
        human1.Height = 160 * 1000;

        var specials = new List<string>();
        specials.Add("UltraThrow");
        specials.Add("TarouBarriar");

        var families = new Dictionary<string, string>();
        families["mother"] = "Mother of Ultra";
        families["brother"] = "Ace";

        return human1;
    }
}
クリーンアップ後
public class Human
{
    public virtual string Name { get; set; }
    public virtual int Height { get; set; }

    public static Human Create()
    {
        /* オブジェクト初期化子を使った書き方に変更 */
        var human1 = new Human   
        {
            Name = "Taro",
            Height = 160 * 1000
        };

        /* コレクション初期化子を使った書き方に変更 */
        var specials = new List<string>   
        {
            "UltraThrow",
            "TarouBarriar"
        };

        /* コレクション初期化子を使った書き方に変更
           (C# 6.0 / Visual Studio 2015以降でのみ可能な書き方) */
        var families = new Dictionary<string, string>   
        {
            ["mother"] = "Mother of Ultra",
            ["brother"] = "Ace"
        };

        return human1;
    }
}

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 全般 > 式の優先順位
オブジェクト初期化子を優先する
コレクション初期化子を優先する

WS000811.PNG

editorconfigで対応する設定:
dotnet_style_collection_initializer, dotnet_style_object_initializer

# 式レベルの設定
dotnet_style_collection_initializer = true:suggestion
dotnet_style_object_initializer = true:suggestion

参考ページ:
式レベルの基本設定 (EditorConfig での .NET の言語規則 - Visual Studio)

14. 式/ブロック本体の基本設定を適用します

メソッド本体やプロパティなどの本体が1行に収まる場合に、従来の形式で記述するか、式形式で記述するかを統一します。

クリーンアップ前
public class Class1
{
    private string FirstName { get; set; }
    private string FamilyName { get; set; }
    public virtual string Name { get { return string.Format("{0} {1}", FamilyName, FirstName); } }

    private string _nickname = null;
    public virtual string NickName
    {
        get
        {
            return (_nickname ?? Name);
        }
        set
        {
            _nickname = value;
        }
    }
}
クリーンアップ後
public class Class1
{
    private string FirstName { get; set; }
    private string FamilyName { get; set; }
    /* Nameプロパティは式本体を1行で書けるため、式形式に変換される
       (C# 6.0 / Visual Studio 2015以降でのみ可能な書き方) */
    public virtual string Name => string.Format("{0} {1}", FamilyName, FirstName);

    private string _nickname = null;
    public virtual string NickName
    {
        /* get, setの両方を持つプロパティにも対応可能
           (C# 7.0 / Visual Studio 2017以降でのみ可能な書き方) */
        get => (_nickname ?? Name);
        set => _nickname = value;
    }
}

設定によってはコンストラクタ、ローカル関数などプロパティ以外の対象にも適用可能です。
ただし、項目によっては対応するC#のバージョンが異なることに注意してください。
(全項目を使用可能になるのはC# 7.0以降(Visual Studio 2017以降))

Visual Studio 2019で対応する設定:
オプション > テキスト エディター > C# > コードスタイル > 全般 > 式の優先順位 > ○○に式本体を使用する

WS000810.PNG

editorconfigで対応する設定:
csharp_style_expression_bodied_***

# 式のようなメンバー
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent

参考ページ:
式形式のメンバー (EditorConfig での .NET の言語規則 - Visual Studio)

補足:ルール選択にかかわらず常に実行される処理

下記の処理は、どのようなルールを選択したかにかかわらず、クリーンアップ時は常に実行されます。

  • コードのフォーマット(メニューの 編集 > 詳細 > ドキュメントのフォーマット と同じ)

補足:そのほか、知っておくと便利な知識

  • コードクリーンアップのための設定内容は、Visual Studioの「設定から .editorconfig を生成」機能を使うことで、ほかの人と共有することができます。
    (メニューの オプション > テキスト エディター > C# > コード スタイル から実行可能です)

    WS000837.PNG

    ただし、残念ながら「どのクリーンアップルールを選択したか」の情報は共有できないため、これだけは別途共有する必要があります。
    (例:「不要な using の削除」をオンにしているかオフにしているかの情報は .editorconfig には含まれないため、各開発者がコードクリーンアップの設定ダイアログからオン/オフを切り替えなくてはなりません)

  • Microsoftの公式ドキュメントでも紹介されている拡張機能「Code Cleanup On Save」を使うと、ファイルを保存するたびに自動でクリーンアップが実行されるようにできます。

    WS000835.PNG

    WS000834.PNG

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

[C#]ValidationAttributeを継承して2つの項目の大小関係を検証する

これを読んでできること

  • 2つの入力項目を検証し、定義した大小関係を満たしているか検証できる
  • カスタムバリデーションを作れるようになる

ValidationAttributeとは

すべての検証属性の基本クラスとして機能します。
ValidationAttribute クラス - docs.microsoft.com

MSのドキュメントを引用しましたが、これだけではよくわかりませんね。

ValidationAttribute とはフォームからの入力値やモデルオブジェクトのプロパティが正しいかどうかの検証を強制し、また正しさを定義する属性の基底クラスです。
カスタム検証属性を定義する場合はこれを継承していきます。

参考によく使われる検証属性を以下に記載します。

属性名 機能
Required 必須項目。nullや未入力の場合エラーとします
StringLength(int) 最大文字長。指定した文字数を超えるとエラーとします
EmailAddress メールアドレス形式の項目。メールアドレスの形式を満たさない場合エラーとします

System.ComponentModel.DataAnnotations には標準で複数の検証属性が実装されています。しかし、開始時刻と終了時刻のように他のプロパティに依存する検証属性はありません1。そのためカスタム検証属性を作る必要があります。

カスタム検証属性を作ってみた

結果として、検証属性を4種類作りました。
どれもほぼ内容が同じなので「AよりBが大きい」を検証する GreaterThanAttribute 2のソースコードを添付します。

GreaterThanAttribute.cs
[AttributeUsage(AttributeTargets.Property)]
    public class GreaterThanAttribute : ValidationAttribute
    {
        public string OtherProperty { get; private set; }
        public string OtherPropertyDisplayName { get; internal set; }

        public GreaterThanAttribute(string otherProperty)
        {
            OtherProperty = otherProperty;
            ErrorMessage = "{0}は{1}より大きい値を指定してください。";
        }

        public override string FormatErrorMessage(string name)
        {
            // エラーメッセージを返す
            return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, OtherPropertyDisplayName ?? OtherProperty);
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // 比較対象のPropertyInfo
            PropertyInfo propertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
            // 比較対象のプロパティの値
            object propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
            Type type = propertyInfo.PropertyType;

            if (type == typeof(DateTime))
            {
                // ここで値の比較。条件を満たしていれば検証成功を返す
                if ((DateTime)value > (DateTime)propertyValue)
                {
                    return ValidationResult.Success;
                }
            }
            else if (type == typeof(int))
            {
                if ((int)value > (int)propertyValue)
                {
                    return ValidationResult.Success;
                }
            }
            // ...other type

            if (OtherPropertyDisplayName == null)
            {
                OtherPropertyDisplayName = GetDisplayNameForProperty(validationContext.ObjectType, OtherProperty);
            }

            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }

        // 比較対象のプロパティ名を取得する。ここはオマケなので削っても問題ない
        private static string GetDisplayNameForProperty(Type containerType, string propertyName)
        {
            ICustomTypeDescriptor typeDescriptor = GetTypeDescriptor(containerType);
            PropertyDescriptor property = typeDescriptor.GetProperties().Find(propertyName, true);

            if (property == null)
            {
                throw new ArgumentException();
            }

            IEnumerable<Attribute> attributes = property.Attributes.Cast<Attribute>();
            DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault();

            if (display != null)
            {
                // DisplayAttributeがついてたらその名称を返す
                return display.GetName();
            }

            DisplayNameAttribute displayName = attributes.OfType<DisplayNameAttribute>().FirstOrDefault();

            if (displayName != null)
            {
                // DisplayNameAttributeがついてたらその名称を返す
                return displayName.DisplayName;
            }

            return propertyName;
        }

        private static ICustomTypeDescriptor GetTypeDescriptor(Type type)
        {
            return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
        }
    }

カスタム検証属性を使用するモデル側は以下の通りです。

Model.cs
        // 開始時刻
        // LessThanAttributeのコードは割愛
        [LessThan("EndDateTime")]
        public DateTime StartDateTime { get; set; }

        // 終了時刻
        [GreaterThan("StartDateTime")]
        public DateTime EndDateTime { get; set; }

使い方

開始時刻 < 終了時刻 であることを検証したい場合、 StartDateTime[LessThan] 属性、 EndDateTime[GreaterThan] 属性を付けます。引数には比較対象のプロパティを string で指定します。Compare 属性と似ていますね。

属性をつけることでASP.NET MVCのモデル検証Blazorのフォーム検証時に大小関係が正しいか検証されます。大小関係を満たさない場合、「StartDateTimeはEndDateTimeより小さい値を指定してください。」や「EndDateTimeはStartDateTimeより大きい値を指定してください。」がエラーメッセージとして表示されます。

おわりに

もっと良い書き方があるような気もしますが、一旦ここで切り上げます。型の分岐とかもっとイケてる書き方3ができそうですね。元気があるときに書き直したいと思います。

参考


  1. Compare 属性がありますが、これは同値のみを検証するため今回のような大小関係を検証する用途では使用できません。 

  2. シェルの gt よりこの名称を採用。他には GreaterThanOrEqual LessThan LessThanOrEqual を定義。それぞれ ge lt leから。 

  3. C# 8.0ではswitch式が使えるのでこのあたりをうまくやれば・・・ 

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

# NPocoの紹介 -機能編- ページング

NPocoの紹介 -機能編- ページング

最初の記事はこちら

今回はページング機能について紹介します。
(「全115件中11~20件目を表示」とかの機能を実現するためのものです。)

公式ドキュメントだとこちらに記載されています。

データベースはPostgreSQLを使用しました。

テーブルの用意

emp

前回と同じものを流用

自動クエリ の場合

生SQLを使わない自動生成クエリの場合、終端操作でToPage メソッドを呼ぶことでページングを実現できます。

Page<Emp> page = database.Query<Emp>()
                        .Where(x => x.DeptCode == "001")
                        .OrderBy(x => x.EmpId)
                        .ToPage(2, 10);

以下のクエリが実行されます。

SELECT COUNT(*) FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
         -> @p0 [String] = "001"


SELECT "E"."emp_id" as "EmpId", "E"."first_name" as "FirstName", "E"."family_name" as "FamilyName", "E"."dept_code" as "DeptCode", "E"."created_at" as "CreatedAt", "E"."updated_at" as "UpdatedAt"
FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
ORDER BY "EmpId" ASC
LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

全件数取得のクエリと、現在のページ部分のデータを取得するクエリの2つが実行されます。

ToPage の第1引数はページ番号(1スタート, 0じゃないよ)
第2引数は1ページあたりの件数です。

上記の引数だと「1ページ当たり10件で2ページ目を取得」なので
LIMIT 10 OFFSET 10 というクエリが生成されています。

またOrderBy で常に同じ並び順になるように注意してください。

Page<> について

ソースはこちら

以下のプロパティが定義されています。

  • CurrentPage 現在のページ(メソッドの第1引数と同じ)
  • TotalPages 総ページ数
  • TotalItems 総件数
  • ItemsPerPage 1ページ当たりの件数(メソッドの第2引数と同じ)
  • Items 現在のページのレコード

Limit メソッドについて

Limit メソッドを使っても 似たようなことが可能です。

List<Emp> list = database.Query<Emp>()
                        .Where(x => x.DeptCode == "001")
                        .OrderBy(x => x.EmpId)
                        .Limit(10, 10)
                        .ToList();

以下のクエリが実行されます。

SELECT "E"."emp_id" as "EmpId", "E"."first_name" as "FirstName", "E"."family_name" as "FamilyName", "E"."dept_code" as "DeptCode", "E"."created_at" as "CreatedAt", "E"."updated_at" as "UpdatedAt"
FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
ORDER BY "EmpId" ASC
LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

ただしこちらは、第1引数はスキップするレコード数となっており、戻り値もList<> となっています。

生SQLを記述する場合

生SQLを使う場合Page メソッドを呼ぶことでページングを実現できます。

string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

Page<Emp> page = database.Page<Emp>(2, 10, sql, param);

以下のクエリが実行されます。

SELECT COUNT(*) FROM (SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
) npoco_tbl
         -> @p0 [String] = "001"

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

クエリ自動生成の場合と同じく全件数取得のクエリと、現在のページ部分のデータを取得するクエリの2つが実行されます。

また同様に Page の第1引数はページ番号、第2引数は1ページあたりの件数です。

このページングについては
- Database.BuildPageQueries
- PagingHelper
- DatabaseType.BuildPageQuery (データベース別のオーバーライドあり)

によって実現されています。

クエリには必ずORDER BYの記述を忘れないようにしてください。

Fetch メソッドについて

実は Fetchメソッドもオーバーライドされており、ページングのクエリを発行できます。
ただしこちらの戻り値は List<> であり、件数取得は行われません。

string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

List<Emp> list = database.Fetch<Emp>(2, 10, sql, param);

以下のクエリが実行されます。

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

Page と同じく第1引数はページ番号です。

SkipTake メソッドについて

SkipTake メソッドでもFetchメソッドと似たようなことは可能です。
ただしこちらの第1引数は「スキップする件数」です。

string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

List<Emp> list = database.SkipTake<Emp>(10, 10, sql, param);

以下のクエリが実行されます。

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

注意点

非常に便利なページングの機能ですが注意点があります。

生SQLを使う場合 with 句を使うことができません。
(クエリ加工を正規表現で行っており、対応していない)

なのでwith を使わないクエリを書くか、with を使う場合は自前のページングをする、ビューを作成する などの対応が必要となります。

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

NPocoの紹介 -機能編- ページング

NPocoの紹介 -機能編- ページング

最初の記事はこちら

今回はページング機能について紹介します。
(「全115件中11~20件目を表示」とかの機能を実現するためのものです。)

公式ドキュメントだとこちらに記載されています。

データベースはPostgreSQLを使用しました。

テーブルの用意

emp

前回と同じものを流用

自動クエリ の場合

生SQLを使わない自動生成クエリの場合、終端操作でToPage メソッドを呼ぶことでページングを実現できます。

Page<Emp> page = database.Query<Emp>()
                        .Where(x => x.DeptCode == "001")
                        .OrderBy(x => x.EmpId)
                        .ToPage(2, 10);

以下のクエリが実行されます。

SELECT COUNT(*) FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
         -> @p0 [String] = "001"


SELECT "E"."emp_id" as "EmpId", "E"."first_name" as "FirstName", "E"."family_name" as "FamilyName", "E"."dept_code" as "DeptCode", "E"."created_at" as "CreatedAt", "E"."updated_at" as "UpdatedAt"
FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
ORDER BY "EmpId" ASC
LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

全件数取得のクエリと、現在のページ部分のデータを取得するクエリの2つが実行されます。

ToPage の第1引数はページ番号(1スタート, 0じゃないよ)
第2引数は1ページあたりの件数です。

上記の引数だと「1ページ当たり10件で2ページ目を取得」なので
LIMIT 10 OFFSET 10 というクエリが生成されています。

またOrderBy で常に同じ並び順になるように注意してください。

Page<> について

ソースはこちら

以下のプロパティが定義されています。

  • CurrentPage 現在のページ(メソッドの第1引数と同じ)
  • TotalPages 総ページ数
  • TotalItems 総件数
  • ItemsPerPage 1ページ当たりの件数(メソッドの第2引数と同じ)
  • Items 現在のページのレコード

Limit メソッドについて

Limit メソッドを使っても 似たようなことが可能です。

List<Emp> list = database.Query<Emp>()
                        .Where(x => x.DeptCode == "001")
                        .OrderBy(x => x.EmpId)
                        .Limit(10, 10)
                        .ToList();

以下のクエリが実行されます。

SELECT "E"."emp_id" as "EmpId", "E"."first_name" as "FirstName", "E"."family_name" as "FamilyName", "E"."dept_code" as "DeptCode", "E"."created_at" as "CreatedAt", "E"."updated_at" as "UpdatedAt"
FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
ORDER BY "EmpId" ASC
LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

ただしこちらは、第1引数はスキップするレコード数となっており、戻り値もList<> となっています。

生SQLを記述する場合

生SQLを使う場合Page メソッドを呼ぶことでページングを実現できます。

string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

Page<Emp> page = database.Page<Emp>(2, 10, sql, param);

以下のクエリが実行されます。

SELECT COUNT(*) FROM (SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
) npoco_tbl
         -> @p0 [String] = "001"

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

クエリ自動生成の場合と同じく全件数取得のクエリと、現在のページ部分のデータを取得するクエリの2つが実行されます。

また同様に Page の第1引数はページ番号、第2引数は1ページあたりの件数です。

このページングについては
- Database.BuildPageQueries
- PagingHelper
- DatabaseType.BuildPageQuery (データベース別のオーバーライドあり)

によって実現されています。

クエリには必ずORDER BYの記述を忘れないようにしてください。

Fetch メソッドについて

実は Fetchメソッドもオーバーライドされており、ページングのクエリを発行できます。
ただしこちらの戻り値は List<> であり、件数取得は行われません。

string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

List<Emp> list = database.Fetch<Emp>(2, 10, sql, param);

以下のクエリが実行されます。

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

Page と同じく第1引数はページ番号です。

SkipTake メソッドについて

SkipTake メソッドでもFetchメソッドと似たようなことは可能です。
ただしこちらの第1引数は「スキップする件数」です。

string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

List<Emp> list = database.SkipTake<Emp>(10, 10, sql, param);

以下のクエリが実行されます。

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"

注意点

非常に便利なページングの機能ですが注意点があります。

生SQLを使う場合 with 句を使うことができません。
(クエリ加工を正規表現で行っており、対応していない)

なのでwith を使わないクエリを書くか、with を使う場合は自前のページングをする、ビューを作成する などの対応が必要となります。

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