rksoftware

Visual Studio とか C# とかが好きです

メンバーと継承の少しだけマニアックな話

C# 11 で static virtual メンバーというものが出てきました。この機能を確認していて、意外と皆さんメンバーと継承の挙動をちゃんと把握せずにいる方もいるのではないか、と思いまとめてみようかと思いました。

正直、継承の挙動をちゃんと把握し書ければならない状況、そうそうないのでちゃんと把握していなくても全く問題ないと思います。たぶん趣味の領域です。

■ abstract メンバー

abstract メンバー実装時の挙動を見てみましょう。

エラーのない状態

こんな感じです。

Console.WriteLine(Add(new CClass())); // 1

int Add(AAbstract @abstract) => @abstract.Add();

abstract class AAbstract { abstract public int Add(); }

class CClass : AAbstract { public override int Add() => 1; }

導出クラスで実装がない場合

導出クラスとは派生先とかサブクラスとか継承子とかとも言われる存在です。導出クラスに実装がない場合はエラーになります。

abstract class AAbstract { abstract public int Add(); }

class CClass : AAbstract { }  // エラー CS0534 'CClass' は継承抽象メンバー 'AAbstract.Add()' を実装しません
エラー CS0534 'CClass' は継承抽象メンバー 'AAbstract.Add()' を実装しません

abstract メンバーを default 実装しようとした場合

abstract メンバーは実装を持てません。

abstract class AAbstract { abstract public int Add() => 1; }  // エラー CS0500 'AAbstract.Add()' は abstract に指定されているため本体を宣言できません
エラー CS0500 'AAbstract.Add()' は abstract に指定されているため本体を宣言できません

多段階の継承がある場合

シンプルに実体クラスの実装が呼ばれます。

Console.WriteLine(Add(new CClass1())); // 1
Console.WriteLine(Add(new CClass2())); // 2

int Add(AAbstract @abstract) => @abstract.Add();

abstract class AAbstract { abstract public int Add(); }

class CClass1 : AAbstract { public override int Add() => 1; }
class CClass2 : CClass1 { public override int Add() => 2; }

abstract メンバーについてはこんな感じです。abstract メンバーを使うことは通常ないでしょうから、へーそうなんだー、と思っておいていただければと思います。

■ virtual メンバー

次に virtual メンバーの override 時の挙動を見てみましょう。

virtual メンバーに実装がない場合

当たり前ですが、abstract メンバーと違い virtual メンバーには実装が必要です。

class CClass { virtual public int Add(); }  // エラー CS0501 'CClass.Add()' は abstract、extern、または partial に指定されていないため、本体を宣言する必要があります
エラー CS0501 'CClass.Add()' は abstract、extern、または partial に指定されていないため、本体を宣言する必要があります

導出クラスに実装がない場合

導出クラスに実装はなくても OK です。

class CClass { virtual public int Add() => 1; }

class CClass1 : CClass { }

基底クラスの型で呼ばれる場合

基底クラスとは基本クラスとかスーパークラスとか継承親とかとも言われる存在です。シンプルに実体クラスの実装が呼ばれます。

Console.WriteLine(Add(new CClass())); // 1
Console.WriteLine(Add(new CClass1())); // 2

int Add(CClass @class) => @class.Add();

class CClass { virtual public int Add() => 1; }

class CClass1 : CClass { public override int Add() => 2; }

複数のクラスを基底にしようとした場合

複数のクラスを基底にすることはできません。

class CClass1 { virtual public int Add() => 1; }
class CClass2 { virtual public int Add() => 2; }

class CClass3 : CClass1, CClass2 { }  // エラー CS1721 クラス 'CClass3' は複数の基底クラス ('CClass1' と 'CClass2') を持つことができません
エラー CS1721 クラス 'CClass3' は複数の基底クラス ('CClass1' と 'CClass2') を持つことができません

virtual メンバーはこんな感じです。virtual メンバーは普段使うこともあるでしょうから、へーそうなんだー、とわかっていただければ良いかと思います。

■ interface の実装

最後に interface の実装時の挙動を見てみましょう。

実装クラスに実装がない場合

interface を実装するクラスに interface メンバーの実装がない場合はエラーになります。通常は。通常じゃないパターンは次の項目で説明します。

interface IInterface { int Add(); }

class CClass : IInterface { }  // エラー CS0535 'CClass' はインターフェイス メンバー 'IInterface.Add()' を実装しません
エラー CS0535 'CClass' はインターフェイス メンバー 'IInterface.Add()' を実装しません

インターフェイスのデフォルト実装

前項目で例外があると言いました、それがこの項目です。interface メンバーのデフォルト実装という機能があります。デフォルト実装をした次のコードは実行できます。

Console.WriteLine(Add(new CClass()));  // 1

int Add(IInterface @interface) => @interface.Add();

interface IInterface { int Add() => 1; }

class CClass : IInterface { }

複数のデフォルト実装

この辺りから少しマニアックになってきます。同名のデフォルト実装を持った複数の interface を実装した場合どうなるでしょうか? ちなみに class の場合は、複数のクラスを継承することはできないので考える必要がありません。
この場合、メソッドを呼び出した時の型の実装が呼ばれます。次の例では IInterface1 型の変数の Add メソッドを呼び出しているので IInterface1 型の実装が呼ばれます。

Console.WriteLine(Add(new CClass())); // 1

int Add(IInterface1 @interface) => @interface.Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { }

クラスに実装があった場合

クラスに実装があっても同じく、interface の型の変数で呼ばれていてデフォルト実装もある場合は、デフォルト実装が呼ばれます。

Console.WriteLine(Add(new CClass())); // 1

int Add(IInterface1 @interface) => @interface.Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { int Add() => 3; }

明示的なインターフェイスの実装

デフォルト実装は実装クラスからどうこうできないのでしょうか? この辺りからかなりマニアックになってきます。
明示的なインターフェイスの実装を行うと実装クラスの実装が呼ばれます。

Console.WriteLine(Add(new CClass())); // 3

int Add(IInterface1 @interface) => @interface.Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; }

IInterface2 には影響なし

当然ですが、IInterface1 の明示的な実装をしても、IInterface2 のデフォルト実装には影響がありません。

Console.WriteLine(Add(new CClass())); // 2

int Add(IInterface1 @interface) => ((IInterface2)@interface).Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; }

IInterface2 も明示的に実装すればクラスの実装が呼ばれます。

Console.WriteLine(Add(new CClass())); // 4

int Add(IInterface1 @interface) => ((IInterface2)@interface).Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; int IInterface2.Add() => 4; }

明示的なインターフェイスの実装だけのクラスの型で呼ぶ場合

呼べません。

int Add(IInterface1 @interface) => ((CClass)@interface).Add();  // エラー CS1061 'CClass' に 'Add' の定義が含まれておらず、型 'CClass' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Add' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; int IInterface2.Add() => 4; }
エラー CS1061 'CClass' に 'Add' の定義が含まれておらず、型 'CClass' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Add' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください

明示的なインターフェイスの実装でないクラスでの実装が必要です。実装すればクラスでの実装が呼ばれます。

Console.WriteLine(Add(new CClass())); // 5

int Add(IInterface1 @interface) => ((CClass)@interface).Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add() => 2; }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; int IInterface2.Add() => 4; public int Add() => 5; }

片方にデフォルト実装がない場合

デフォルト実装のない interface のために通常のクラスでの実装が必要です。

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add(); }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; }  // エラー CS0535 'CClass' はインターフェイス メンバー 'IInterface2.Add()' を実装しません
エラー CS0535 'CClass' はインターフェイス メンバー 'IInterface2.Add()' を実装しません

実装するとこんな感じです。

Console.WriteLine(Add(new CClass())); // 5

int Add(IInterface1 @interface) => ((IInterface2)@interface).Add();

interface IInterface1 { int Add() => 1; }
interface IInterface2 { int Add(); }

class CClass : IInterface1, IInterface2 { int IInterface1.Add() => 3; public int Add() => 5; }

ちょっとマニアックでしたね

interface の挙動は最後ちょっとマニアックでしたね。しかもせっかくなので、へーそーなのかー、くらいに受け止めて覚えておいていただけたらと思います。

Power Automate を始める際に最初に学ぶ 10 のこと

■ 最初に学ぶこと

どんなものを学習するにしても、最初に知っておくことは多数あります。Power Automate でもそれは変わりません。Power Automate は既に普通に使われていますが、これから始める方に向けた情報についてはしかし、まだまだこれからのプロダクトだと思います。

知らずに始めると大変なことになることを知らずに始めてしまう方も少なくないと思います。私はそうでした。そこで私が最初に知ってきたかった基本的なことをまとめてみます。

■ 目次

  1. エラー処理
  2. ノーコード・ローコードではない。大量のコードが必要
  3. 変数を使う必要はない。イミュータブルでできる
  4. 複数の定数の宣言
  5. スコープを絞れない
  6. ソリューションは必須
  7. 式をコピペする
  8. 条件の書き方
  9. null との永遠の戦い
  10. データ操作を全て確認しておく

■ エラー処理

IT の世界でまず真っ先に学ぶことは何でしょう? そう、エラー処理 ( 例外処理 ) です!
それは Power Automate の世界でも同じです。

エラー処理 ( 例外処理 ) とは?

IT の世界では何らかの業務処理は必ず失敗をする可能性があります。その際にコンピュータは個人の労力の持ち出しで良い感じに辻褄を合わせたりはしません。失敗したときにどうするかを必ず決めておかなければなりません。必ずです。

必ずです。

必ずです。これを設計しないことはあり得ません。Power Automate でも同じです。

良くあるコードでは

多くのプログラミング環境で、例外という機能があります。try catch finally という言い方をすれば多くのプログラミング言語話者に通じると思います。

Power Automate では

スコープ というアクションと 実行条件の構成 を使います。

  1. エラーを検知したい処理を スコープ の中に作る
  2. エラーになった場合の処理を スコープ の次に置く
  3. エラーになった場合の処理の 実行条件の構成 を設定する
  4. エラー時の動作を設定する ( ここではエラー発生をメールで通知しています )

こんな感じです。

{
 "result": "@{result('スコープ')}",
 "フローの名前": "@{workflow()?['tags/flowDisplayName']}",
 "フローの詳細画面の URL": "https://make.powerautomate.com/manage/environments/@{workflow()['tags']['environmentName']}/flows/@{workflow()['name']}/details",
 "フローの実行履歴詳細の URL": "https://make.powerautomate.com/manage/environments/@{workflow()['tags']['environmentName']}/flows/@{workflow()['name']}/runs/@{workflow()['run/name']}"
}

この通知内容で、どのフローでエラーが起きたのか、そしてフローの詳細画面や実行履歴詳細への URL を通知できます。テンプレートとしてコピペしてしまってください。基本中の基本です。

■ ノーコード・ローコードではない。大量のコードが必要

Power Automate では に大量のコードを書くことになります。
例えばある文字列の配列のそれぞれの要素に対して、その値が pre_{4 桁の連番} というデータだった場合に連番を一つ増やした値に置き換えるということを考えてみます。

投入データと結果

こんな感じでやってみます。
投入データ ["pre_0002", "no"] → 出力データ ["pre_0003", "no"]

C# の場合

私が得意なので C# で書いてみます。こんな感じですね。とてもシンプルで分かりやすく書けています。

const string pre = "pre_";  new[] { "pre_0002", "no" }.Select(s => (s.StartsWith(pre) && int.TryParse(s.Substring(pre.Length), out var i)) ? $"{pre}{i + 1:0000}" : s);

Power Automate の式の場合

正直厳しいと言わざるを得ません。

if(and(startsWith(item(), 'pre_'), isInt(slice(item(), min(length(item()), length('pre_'))))), concat('pre_', slice(concat('000', add(int(slice(item(), min(length(item()), length('pre_')))), 1)), sub(length(string(int(slice(item(), min(length(item()), length('pre_')))))), 1))), item())


コードの横スクロールバーの長さの違いだけでもかなりの差が出ています。

つまり

Power Automate では、プロ開発に比べてかなり難しいコードを、プロ開発よりはるかに多いコード量書かなければなりません。最初から大量の難しいコードを書く前提で挑む必要があります。

■ 変数を使う必要はない。イミュータブルでできる

Power Automate には 作成 というイミュータブル値を作るアクションがあります。
名前が分かりづらいので、「イミュータブル」とかに名前を変更して欲しいと常々思っています。

できるだけイミュータブルにすべきという話

値はできるだけイミュータブル ( 変更不可能な ) にすべきというのは現代の共通認識です。変更可能な値 ( 変数 ) は意図せぬ変更がされるコードは含まれてしまう可能性がありバグの元です。それは当然 Power Automate でも同じです。基本中の基本です。

■ 複数の定数の宣言

複数の定数を宣言する場合、作成 を複数使うのではなく、JSON の解析 を使います。
これにより、

  • アクションの箱が少なくなってフロー見通しが良くなる
  • アクションの数が少なくなってアクション要求数が減り実行にかかるお金も節約できる

という大きな効果があります。

フローの見通しの差

作成 では値の数だけアクションの箱を並べることになります。しかし JSON の解析 ならアクションの箱は 1 つだけで済みます。


この例では定数は 2 つだけですが、定数を 10 つ 20 つと増えたときの前者の絶望感は想像は難しくないと思います。

作成 の場合

JSON の解析 の場合

■ スコープを絞れない

Power Automate の変数等はスコープを絞れません。一つのフローの中で常トップレベルです。また、変数はトップレベルの階層にしか置けません。

スコープを絞れない

一つのフローの中である限り回避方法はありません。あきらめましょう。

トップレベルにしか置けない

作成 はトップレベルでないところにも置けるので、できるだけ 変数 ではなく、作成 を使うことで制約を避けられます。

常にグローバル

別のスコープであっても同じ名前の 作成 は作れません。

深い階層で作られた値をどの階層でも使えます。

■ ソリューションは必須

ソリューションを使うことで、親子フローを作ることができます。
親フローから子フローを呼び出して、子フローの処理が終わったら親フローに戻ってきます。この機能を使うことで変数のスコープを疑似的に絞ることができます。

例えばこんなフロー

繰り返しの処理の中で作られた値を配列にしたい場合です。繰り返しの外、トップレベルで 変数 を宣言しなければなりません。ただ、繰り返しの処理結果を格納したいだけなのに。

※ この例であれば 選択 アクションでも事足りますがあくまで例なので、選択 ではすまない処理があると思ってください。

親子フローを使えば

親子フローを使えば、変数 の生存範囲を子フローの中に絞ることができます。

コンカレンシー制御にも

この 変数 制約の回避や、子フローの再利用性によって Power Automate で並列実行処理を使えるようになります。 コンカレンシー制御 という機能もこの 変数 の制約回避によって活かせるようになってきます。
コンカレンシー制御 の他にも並列実行の技術はありますが、その際も子フローの再利用性が効いてきます。

■ 式をコピペする

式を直接書くことは実は多くありません。大体コピペします。しかし単純ではありません。

まず狭い

まず式の入力欄はとても狭いです。例えば前述の簡単な処理の式など。ここでコーディングすることは正直厳しいと言わざるを得ません。

if(and(startsWith(item(), 'pre_'), isInt(slice(item(), min(length(item()), length('pre_'))))), concat('pre_', slice(concat('000', add(int(slice(item(), min(length(item()), length('pre_')))), 1)), sub(length(string(int(slice(item(), min(length(item()), length('pre_')))))), 1))), item())

文字列を構成するときなども

文字列の中に式を書く場合も。

テキストエディタなどで書いて

Power Automate の編集の GUI 内でコピペできることはなんとなく触ればわかるかもしれません。しかしそれだけではすまないのが現実です。いくつかの定型式を用意しておいて適宜使ったり、Visual Studio Code やメモ帳などでコーディングした式を貼り付けたり。
実は GUI でコピーした式をテキストとして Visual Studio Code やメモ帳などに貼り付けできます。その際の形式が次のようなテキストです。

@{utcNow()}

@{} の間に式が書かれています。そして、この形式のテキストをペーストすれば前述の画像のような GUI で設定したものと同じように張り付きます。

この Visual Studio Code やメモ帳で @{} の間に式を書いて、コピーしてペースト。これがなければまともに式は取り扱えません。覚えておきましょう。基本中の基本です。

■ 条件の書き方

条件 は Power Automate でフローを作る際、ほぼ必須のアクションですがその扱いは非常に難しいと言わざるを得ません。

条件の設定の見た目


これを見て騎乗デバッグできる方もいないことはないと思いますが、相当のセンスをお持ちだと思います。少なくとも私には厳しいと言わざるを得ません。さらに 条件 では コードのプレビュー もできないのがまたつらいところです。

true 次の値に等しい

そんな時は、条件の設定の左辺に true、真ん中の比較の選択肢を 次の値に等しい に設定します。そして and 関数や or 関数を使って一つの式で全部の条件を書きます。

or(
  and(
    equals(outputs('作成')?[0], 1),
    greater(outputs('作成')?[1], 3)
  ),
  and(
    not(equals(outputs('作成')?[0], 1)),
    less(outputs('作成')?[1], 0)
  )
)


式を見る際に ( コピペで Visual Studio Code やメモ帳に貼り付ける必要はあるでしょうが ) 非常に把握しやすくなります。

■ null との永遠の戦い

Power Automate はとにかく null でエラーになります。Power Automate による実装作業は null との闘いの作業と言っても過言ではありません。

関数の引数に null を渡すとまずエラーになる

関数の引数として null を受け取るとエラーになる関数が非常に多くあります。まずエラーになるという覚悟でいた方が良いでしょう。例えば文字列の長さを調べられる length 関数も。0 を返してくれてもいいじゃない、と思うのですがそうはいきません。無慈悲にエラーです。慈悲はない。

こんなフローが

エラーになります。

文字列が数値に見えるかの関数

文字列が数値に見えるかの関数も無慈悲にエラーです。慈悲はない。false 返してくれたらうれしいのに。

じゃあどうするのか

equals 関数で null かどうかを評価し if 関数で分岐します。これを至る所で書くのが Power Automate の流儀です。慈悲はない。

@{if(equals(outputs('作成')?[1], null), false, isInt(outputs('作成')?[1]))}

こんな感じです。一つフローを作るのに何回、何十回と書くことになります。定型として覚えてしまいましょう。

■ データ操作を全て確認しておく

Power Automate は式を大量に書くのが特徴であることは前述しました。しかしお世辞にも関数の品ぞろえが良いとは言えないのもまた大きな特徴です。正直厳しいと言わざるを得ません。

しかし関数は把握しやすい

しかし関数がどんなものがあるのか把握しやすいのも Power Automate の特徴です。次のような感じで GUI で簡単にどんな関数があるか一覧できます。一覧の長さも非常に短いのでそれもまた把握しやすい要因です。

でも足りない

でも足りないというのが正直なところです。正直足りないと言わざるを得ないこともまた特徴です。
そんな時にもしかしたら助かるかもしれないのが データ操作 というカテゴリのアクションです。これでも十分というにはほど遠いのが正直なところですが、関数では力不足なちょっとしたことができたりします。

今日日、一般的なプログラミング言語では filtermap などが存在しないことなど考えられないと思います。C# でいえば WhereSelect です。これらは当然 Power Automate にもあります。しかし関数ではなくアクションとして存在しているので気が付くのに時間がかかってしまうのが厄介なところです。Power Automate では filterアレイのフィルター処理map選択 というアクションになっています。

このように基本的な機能が データ操作 に存在しています。まず真っ先にここにどのような機能があるのか確認しておきましょう。

■ まとめ

Power Automate の最初の一歩で読む情報たちになかなか無いものの最初に知っておかないと非常に厳しいと言わざるを得ない、10 つの要素をまとめてみました。これで Power Automate を使ってすぐに世界に価値を提供できるようになる人が少しでも増えたらと思います。

この 10 のまとめ、いかがでしたか? 皆さんの思う、最初に知っておくべきこともぜひ教えてください。

Power Automate で四捨五入

■ Power Automate で四捨五入

Power Automate には四捨五入を行ってくれる関数がありません。
そこで、多くの方がいろいろと頑張っているようです。軽く調べてみたら主流はどうも次のような感じみたいです。

※この記事では簡略化のため、整数に四捨五入するパターンのみを考えます。

{
    "inputs": {
        "from": [
            1.5,
            2.5
        ],
        "select": "@formatNumber(decimal(item()), 'F0')"
    }
}


■ 結果が JIS じゃない

この実行結果、JIS の四捨五入になっていません。例えば C# では次のようになります。

Console.WriteLine(Math.Round(1.5, 0));  // 2
Console.WriteLine(Math.Round(2.5, 0));  // 2

たいていの業務で四捨五入といえば、JIS の四捨五入のはずです。金額や寸法、量など。JIS の四捨五入でない業務は私は見たことがりません。

良くある呼び方

JIS の四捨五入は 銀行丸め と呼ぶ方も多いです。

■ 余談

この JIS の四捨五入、システムのバグとして報告されることが非常に多いです。
理由は、Excel がそうなっていないから。

しかし、バグとして報告されるも業務を調べたら JIS の四捨五入が正しい業務だったという確率は 100% です。覚えておくと少し不幸が避けられるかもしれません。

■ Power Automate でやってみよう

if (
  or(greater(mod(item(), 1), 0.5), equals(mod(sub(item(), mod(item(), 1)), 2), 1))
  , float(formatNumber(item(), 'F0'))
  , sub(item(), mod(item(), 1))
)
{
    "inputs": {
        "from": [
            1.5,
            2.5
        ],
        "select": "@if (\r\n  or(greater(mod(item(), 1), 0.5), equals(mod(sub(item(), mod(item(), 1)), 2), 1))\r\n  , float(formatNumber(item(), 'F0'))\r\n  , sub(item(), mod(item(), 1))\r\n)"
    }
}


それっぽい結果になりました。

C# 11 で Generic な算術演算を作る

C# 11 で 「 ジェネリック型数値演算のサポート 」 という新機能が追加されました。その中の 「 インターフェイスの static virtual メンバー 」 を書いて試してみたいと思います。

rksoftware.hatenablog.com

// Console.WriteLine(new SValue1(10) + new SValue1(5)); // エラー CS0019 演算子 '+' を 'SValue1' と 'SValue1' 型のオペランドに適用することはできません
Console.WriteLine(new SValue1(10) - new SValue1(5));  // SValue { Value = 5 }

Console.WriteLine(new SValue2(10) + new SValue2(5));  // SValue2 { Value = 15 }
Console.WriteLine(new SValue2(10) - new SValue2(5));  // SValue1 { Value = 5 }

// Console.WriteLine(new SValue2(10) + new SValue1(5));  // エラー CS0019 演算子 '+' を 'SValue2' と 'SValue1' 型のオペランドに適用することはできません
Console.WriteLine(new SValue2(10) - new SValue1(5));  // SValue1 { Value = 5 }

Console.WriteLine(new SValue3(10) + new SValue3(5));  // SValue2 { Value = 15 }
Console.WriteLine(new SValue3(10) - new SValue3(5));  // SValue1 { Value = 5 }

Console.WriteLine(new SValue4(10) + new SValue4(5));  // SValue4 { Value = 15 }
Console.WriteLine(new SValue4(10) - new SValue4(5));  // SValue4 { Value = 5 }

interface IInterface<T> where T:IInterface<T>
{
    static virtual T operator +(T arg1, T arg2) => arg1;
    static abstract T operator -(T arg1, T arg2);
}

record SValue1(int Value) : IInterface<SValue1>
{
    public static SValue1 operator -(SValue1 arg1, SValue1 arg2) => new SValue1((arg1?.Value??default) - (arg2?.Value??default));
}

record SValue2(int Value) : SValue1(Value)
{
    public static SValue2 operator +(SValue2 arg1, SValue2 arg2) => new SValue2((arg1?.Value ?? default) + (arg2?.Value ?? default));
}

record SValue3(int Value) : SValue2(Value)
{
}

record SValue4(int Value) : SValue3(Value)
{
    public static SValue4 operator -(SValue4 arg1, SValue4 arg2) => new SValue4((arg1?.Value ?? default) - (arg2?.Value ?? default));
    public static SValue4 operator +(SValue4 arg1, SValue4 arg2) => new SValue4((arg1?.Value ?? default) + (arg2?.Value ?? default));
}

こんな感じ。いいですね。

SValue1 の +

SValue1 の + は (virtual メソッドは実装を強制されない、実装していないので) コンパイルエラーです。

SValue2 と SValue1 の演算

型として base よりの SValue1 の演算が使われています。

SValue3 で実装のない演算

SValue2、SValue1 の演算が使われています。

SValue4 同士の演算

SValue4 で実装した演算が使われます。

■ 想像通り

想像通りの素直な分かりやすい結果となりました。いいですね。
ただし、実装がないと base の演算が行われるのでそこは注意ですね。

C# 2.0 以降の新機能概要まとめ (C# 11.0)

C# 2.0 以降の新機能の名前と公式ガイドページへのリンクをまとめました。(C# 11.0)

■ C# 11.0 での新機能

・ファイル スコープ型
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/file  型を修飾子 file で定義することで、同一ファイル内だけで使える型を作れます。

file class Class1 { }
file struct Struct1 { }

// 同一ファイル内では使える
class Class2
{
    void Method()
    {
        new Class1();
        new Struct1();
    }
}
// 別ファイルでは使えない
class Class3
{
    void Method()
    {
        new Class1();   // エラー CS0246 型または名前空間の名前 'Class1' が見つかりませんでした(using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
        new Struct1();  // エラー CS0246 型または名前空間の名前 'Struct1' が見つかりませんでした(using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
    }
}

・ジェネリック型数値演算のサポート - インターフェイスの static virtual メンバー
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/interface#static-abstract-and-virtual-members
 インターフェイスに static virtual メソッド、static abstract メソッドを定義できます。性質は virtualabscract を思い浮かべれば良いのですが、static virtual メソッドを呼ぶことはできないので注意が必要です。

IInterface.Method2(0);  // エラー CS8926 静的な仮想または抽象インターフェイス メンバーには、型パラメーターでのみアクセスできます。

CClass.Method2(0); // OK
CClass.Method3(0);

interface IInterface
{
    static int Method1(int arg) => default;
    static virtual int Method2(int arg) => default;
    static abstract int Method3(int arg);
}

class CClass : IInterface
{
    public static int Method2(int arg) => default;  // これがあると呼び出せる
    public static int Method3(int arg) => default;
}

・ジェネリック型数値演算のサポート - ユーザー定義の checked 演算子
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/arithmetic-operators#user-defined-checked-operators
 演算子のオーバーロードをする際に、checked 用の演算子を定義できます。

Console.WriteLine(new Record(100) + new Record(200));               // Record { Value = 1 }
checked { Console.WriteLine(new Record(100) + new Record(200)); }   // Record { Value = 2 }

record struct Record(int Value)
{
    public static Record operator +(Record arg1, Record arg2) => new(1);
    public static Record operator checked +(Record arg1, Record arg2) { checked { return new(2); } }
}

・ジェネリック型数値演算のサポート - 緩和されたシフト演算子
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#operator-overloadability
 シフト演算子のオーバーロードをする際に、右オペランド (第 2 引数) に int でない型を使えます。独自の方でも OK です。

Console.WriteLine(new Sample() >> 1.0 / 2.0);
Console.WriteLine(new Sample() >> new Dummy());

record struct Dummy { }

record struct Sample
{
    public static Sample operator >>(Sample arg1, int arg2) => new Sample();
    public static Sample operator >>(Sample arg1, double arg2) => new Sample();
    public static Sample operator >>(Sample arg1, Dummy arg2) => new Sample();
}

・ジェネリック型数値演算のサポート - 符号なし右シフト演算子
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#unsigned-right-shift-operator-
 新しい右シフト演算子 >>> で符号なしの右シフトができます。

Console.WriteLine(Convert.ToString(-1, toBase: 2).PadLeft(32, '0'));
Console.WriteLine(Convert.ToString(-1 << 1, toBase: 2).PadLeft(32, '0'));
Console.WriteLine(Convert.ToString(-1 >> 1, toBase: 2).PadLeft(32, '0'));
Console.WriteLine(Convert.ToString(-1 >>> 1, toBase: 2).PadLeft(32, '0'));
// 11111111111111111111111111111111
// 11111111111111111111111111111110
// 11111111111111111111111111111111
// 01111111111111111111111111111111

・auto-default 構造体
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/struct#struct-initialization-and-default-values
 構造体のコンストラクタで初期化されないフィールドおよびプロパティが自動で default で初期化されるようになりました。

Console.WriteLine(null == new Saitama().Omiya);     // True
Console.WriteLine(null == new Saitama().Kawagoe);   // True

struct Saitama
{
    public string Omiya;
    public string Kawagoe { get; set; }
    public Saitama() { }
}

・string 定数での Span<char> のパターン マッチ
 https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/functional/pattern-matching#compare-discrete-values
 Span<char> の値の対するパターンマッチで文字列とマッチできます。

Span<char> saitama = "saitama".ToCharArray();
ReadOnlySpan<char> tokyo = "tokyo".ToCharArray();

Console.WriteLine($"{saitama} is {((saitama is "saitama")? "最高":"もうすこしがんまりましょう")} です。");
Console.WriteLine($"{tokyo} is {((tokyo is "saitama") ? "最高" : "もうすこしがんまりましょう")} です。");
// saitama is 最高 です。
// tokyo is もうすこしがんまりましょう です。

・拡張 nameof スコープ
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/nameof
 メソッドに付ける属性の宣言時に、そのメソッドのパラメータ名で nameof できます。

[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(arg))]
string? Method(string? arg) => arg;

・数値 IntPtr
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/integral-numeric-types#characteristics-of-the-integral-types
 nintSystem.IntPtr の、 nuintSystem.UIntPtr のそれぞれエイリアスになりました。

Console.WriteLine(((nint)0).GetType());     // System.IntPtr
Console.WriteLine(((nuint)0).GetType());    // System.UIntPtr

・UTF-8 の文字列リテラル
 https://learn.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-11#utf-8-string-literals
 文字列の後ろ ( 閉じの " の後ろ ) に u8 を付けると UTF-8 エンコードの System.ReadOnlySpan<T> が得られます。

foreach (var c in "埼玉".ToArray()) Console.Write(c); Console.WriteLine();
foreach (var c in System.Text.Encoding.Unicode.GetBytes("埼玉")) Console.Write(c); Console.WriteLine();
foreach (var c in System.Text.Encoding.UTF8.GetBytes("埼玉")) Console.Write(c); Console.WriteLine();
foreach (var c in "埼玉"u8) Console.Write(c); Console.WriteLine();
// 埼玉
// 25287137115
// 229159188231142137
// 229159188231142137

・必須メンバー
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/required
 クラスや構造体のメンバーを required キーワードで修飾すると必須にできます。必須とは初期化が必須ということで、null で初期化することは OK です。

new Class1 { P1 = null, P2 = null };
new Class1 { P1 = null };  // エラー

class Class1
{
    internal string? P1 { get; init; }
    internal required string? P2 { get; init; }
}

・ref フィールド
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/ref-struct
 ref 構造体のフィールドを ref にできます。

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

・scoped ref
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/declarations#scoped-ref
 ref の値の範囲を現在のスコープに限定します。

static ref Saitama Method1(scoped ref Saitama saitama)
{
    return ref saitama; // エラー CS9075  現在のメソッドに範囲指定されているため、参照渡し 'saitama' でパラメーターを返すことはできません
}

static Saitama Method2(scoped Saitama saitama)
{
    return saitama;  // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'scoped Saitama' を使用することはできません
}

static Saitama Method3(ref string saiko)
{
    scoped Saitama saitama = new(ref saiko);
    return saitama; // エラー CS8352  参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'saitama' を使用することはできません
}

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

・未加工の文字リテラル
 https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/strings/#raw-string-literals
 """ で囲うと改行やエスケープが必要な文字列をそのまま書けます。""" を文字列に含みたい場合は """" で囲みます。"""" を含みたい場合は """"" ......。機能を表す言葉が 未加工の文字リテラル で機能名が 生文字列リテラル のようです。

var ramen = """
    "手打ち風" ラーメン
    \550-
    """;
Console.WriteLine(ramen);
// "手作り風" ラーメン
// \550-
var ramen = """"
    """手打ち風""" ラーメン
    \550-
    """";
Console.WriteLine(ramen);
// """手作り風""" ラーメン
// \550-

・改善された、メソッド グループからデリゲートへの変換
 ※公式 Learn の記事は C# 11 の新機能の記事しか見つけることができませんでした。
 この機能は C# 新機能の記述を読んでも良くわかりませんでした。ジェネリックのメソッドからそうでないデリゲート型への暗黙の変換が行われます? その際にキャッシュされて複数回同じ変換をしてもキャッシュが使われる......? と書いてあるような気がします。

// こういうこと、なのでしょうか?
DInt dint1 = Method<int?>;
DInt dint2 = Method;
DString dstring1 = Method<string?>;
DString dstring2 = Method;

Console.WriteLine(object.Equals(dint1, dint2));         // True
Console.WriteLine(object.Equals(dint1, dstring1));      // False
Console.WriteLine(object.Equals(dstring1, dstring2));   // True

T? Method<T>(T? t) => default;

delegate int? DInt(int? i);
delegate string? DString(string? i);

・"警告ウェーブ 7"
 https://rksoftware.hatenablog.com/entry/2022/12/04/220000
 型名がすべて英小文字だと警告になります。

class saitama { }   // 警告 CS8981    型名 'saitama' には、小文字の ASCII 文字のみが含まれています。このような名前は、プログラミング言語用に予約されている可能性があります。

class saitama1 { }  // 数字が含まれているので OK
class saitamaA { }  // 大文字が含まれているので OK
class saitama_ { }  // 記号が含まれているので OK
class 埼玉 { }      // 非 ASCII 文字が含まれているので OK

・汎用属性
 ※公式 Learn の記事は C# 11 の新機能の記事しか見つけることができませんでした。
 日本語よりも英語の機能の名前の方が分かりやすいと思います。「 Generic attributes 」です。名前のまま。属性のクラスを Generic にできます。

var typeString = typeof(SampleStringClass).GetCustomAttributes(false)[0].GetType();
var typeInt = typeof(SampleIntClass).GetCustomAttributes(false)[0].GetType();
Console.WriteLine(typeString);  // SampleAttribute`1[System.String]
Console.WriteLine(typeInt);     // SampleAttribute`1[System.Int32]

class SampleAttribute<T> : Attribute { }

[Sample<string>] class SampleStringClass { }
[Sample<int>] class SampleIntClass { }

・文字列補間式の改行
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/tokens/interpolated
 文字列補間式の中で改行ができます。

var prefectures = new[] { "埼玉", "東京" };

foreach (var prefecture in prefectures)
    Console.WriteLine(
        $"{prefecture}{
            prefecture switch
            {
                "埼玉" => "最高",
                _ => "もっとがんばりましょう"
            }
        } です。"
    );

// 埼玉 は 最高 です。
// 東京 は もっとがんばりましょう です。

・リスト パターン
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/patterns#list-patterns
 リスト ( や配列など ) の要素でパターンマッチできます。使える型は IList<T>IReadOnlyList<T> 。細かくは int 型のインデクサーと int Count プロパティがある型で使える。

IList<int> data = new[] { 1, 2, 3 };

var result = data switch
{
    [1, 2, 3] => 1,
    _ => 2
};
Console.WriteLine(result);  // 1
// IList<T> 、 IReadOnlyList<T> で使える
IList<int> dataA = new[] { 1, 2, 3 };
IReadOnlyList<int> dataB = new[] { 1, 2, 3 };
Console.WriteLine(dataA is [1, 2, 3]);
Console.WriteLine(dataB is [1, 2, 3]);
// int 型のインデクサーと int Count プロパティがある型で使える
Sample data = new();
Console.WriteLine(data is [1, 2, 3]);

class Sample
{
    public int this[int index]{ get => 0; }
    public int Count => 0;
}

■ 出典/参考文献

 上記まとめの作成に、次のサイトを大いに参考にさせていただきました。
 文言やサンプルコードは C# のガイド のコードを利用させていただきました。

C# のガイド | Microsoft Docs
learn.microsoft.com

Power Automate 条件を快適に設定する方法

Power Automate に限らずたいていのものはコードで管理することがベストプラクティスです。
条件も例外ではありません。

条件

■ 結論

右辺に全部書きます。

左辺に true と書いて右辺に真偽値を返す式を書きます。
これで、式ですべてを表現できるので非常に扱いやすく、見ても分かりやすくなります。
and や or を多段複合した条件も楽になります。ベリーグッド!

■ まず真っ先に知って欲しい

この設定方法。Power Automate 使いだした最初に教えてほしかった方法です。これから Power Automate を始めるすべての人にぜひ最初に知って欲しいです。

Power Automate アレイのフィルター処理を快適に設定する方法の余談 - 条件の場合

以前に Power Automate アレイのフィルター処理を快適に設定する方法 の記事を書きました。

この記事の内容、条件 にも使えるのでは? と思うかもしれません。私は発見したときにあの非常に設定が困難な 条件 でも使えたらかなりの生産性 UP ! と思い、急いで確認しに行きました。

■ 結論

■ 無念です。

条件には 詳細モードで設定 はありません。コードのプレビューもないので何か課題があるのかもしれません。無念です。