技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

リスコフの置換原則とは

リスコフの置換原則とはオブジェクト指向の設計の原則の一つであり、「基本クラスをサブクラスに入れ替えても問題なく動かなけらばならない」という原則です。
もしサブクラスに入れ替えると正しく動かなくなるのであれば、クラスを利用する側はサブクラスを使用してはならないというのを意識しながらコーディングしなければならず、保守性が低下します。
 
クラスの入力・出力に目を向けると、以下の条件を満たすとリスコフの置換原則を満たすことができます。
1.サブクラスの入力のパターンは、基本クラスの入力のパターンよりも緩い
2.サブクラスの出力のパターンは、基本クラスの入力のパターンよりも厳しい
 
もし、サブクラスの入力のパターンが基本クラスの入力のパターンよりも厳しいと、利用する側はサブクラスを利用する際にサブクラスが許容する入力であるかどうかを意識する必要が出てきてしまいます。
また、サブクラスの出力のパターンが基本クラスの出力のパターンよりも緩いと、利用する側はサブクラスを利用する際にサブクラスが予期せぬ出力をしないか意識する必要が出てきてしまいます。
 
以下のサンプルコードは、割り算を行う基本クラスであるDivideクラスを、サブクラスのDivideBusinessクラス・DivideBad1クラス・DivideBad2クラスで入れ替えてみるサンプルです。
 
DivideBusinessクラスは、基本クラスが許容する入力パターンに加え分母=0のパターンも許容し、基本クラスが出力することがある負数を出力しません。Divideクラスと入れ替えても特に考慮するパターンが増えることはなく、リスコフの置換原則を満たした良いクラスです。
しかし、DivideBad1クラスは、基本クラスが許容する分子=負数のパターンを許容せず、基本クラスと入れ替えた場合は分子が負数になっていないか意識する必要が出てきてしまいます。
また、DivideBad2クラスは、基本クラスと異なりnullを出力するパターンがあり、基本クラスと入れ替えた場合は出力がnullになる場合を意識する必要が出てきてしまいます。
 
【サンプルコード】
・DivideMain.java
public class DivideMain {

    public static void main(String[ ] args) {

        Divide divide[ ] = new Divide[4];
        divide[0] = new Divide();
        divide[1] = new DivideBusiness();
        divide[2] = new DivideBad1();
        divide[3] = new DivideBad2();

        for (int i = 0; i < 4; i++) {
            System.out.println("[テストその" + (i + 1) + "]");
            Calc(divide[i],6,2);
            Calc(divide[i],-6,2);
            Calc(divide[i],6,0);
            Calc(divide[i],null,2);
        }
    }

    public static void Calc
        (Divide divide, Integer numerator, Integer denominator) {

        System.out.println("クラス名:" + divide.getClass().getName());
        System.out.println("分子  :" + numerator);
        System.out.println("分母  :" + denominator);

        try {
            Integer result = divide.Calc(numerator, denominator);
            if (result == null) {
                System.out.println("結果  :想定外の値!");
            } else {
                System.out.println("結果  :" + result);
            }

        } catch (Exception e) {
            System.out.println("結果  :想定外の例外!");
        }

        System.out.println("");
    }
}
 
・Divide.java
// 入力:分母はnullや0は不可(例外送出)
// 出力:整数(負になり得る)
public class Divide {

    public Integer Calc(Integer numerator, Integer denominator)
        throws Exception {

        int numeratorTemp;
        int denominatorTemp;

        if (numerator == null) {
            numeratorTemp = 0;
        } else {
            numeratorTemp = numerator;
        }

        if (denominator == null) {
            denominatorTemp = 0;
        } else {
            denominatorTemp = denominator;
        }

        return numeratorTemp / denominatorTemp;
    }
}
 
・DivideBusiness.java
// 入力:分母はnullや0も許容
// 出力:整数(負にはならない)
public class DivideBusiness extends Divide {

    public Integer Calc(Integer numerator, Integer denominator)
        throws Exception {

        int numeratorTemp;
        int denominatorTemp;

        if (numerator == null || numerator < 0) {
            numeratorTemp = 0;
        } else {
            numeratorTemp = numerator;
        }

        if (denominator == null || denominator < 0) {
            denominatorTemp = 0;
        } else {
            denominatorTemp = denominator;
        }

        if (denominatorTemp == 0) {
            denominatorTemp = 1;
        }

        return numeratorTemp / denominatorTemp;
    }
}
 
・DivideBad1.java
// 入力:分子は負数は不可(例外送出)、分母はnullや0未満は不可(例外送出)
// 出力:整数(負になり得る)
public class DivideBad1 extends Divide {

    public Integer Calc(Integer numerator, Integer denominator)
        throws Exception {

        int numeratorTemp;
        int denominatorTemp;

        if (numerator == null) {
            numeratorTemp = 0;
        } else {
            numeratorTemp = numerator;
        }

        if (denominator == null) {
            denominatorTemp = 0;
        } else {
            denominatorTemp = denominator;
        }

        if (numeratorTemp < 0 || denominatorTemp < 0) {
            throw new Exception();
        }

        return numeratorTemp / denominatorTemp;
    }
}
 
・DivideBad2.java
// 入力:分子はnull許容、分母は0は不可(例外送出)
// 出力:整数(負になり得る)、ただし分子や分母がnullの場合はnull
public class DivideBad2 extends Divide {

    public Integer Calc(Integer numerator, Integer denominator)
        throws Exception {

        int numeratorTemp;
        int denominatorTemp;

        if (numerator == null) {
            return null;
        } else {
            numeratorTemp = numerator;
        }

        if (denominator == null) {
            return null;
        } else {
            denominatorTemp = denominator;
        }

        return numeratorTemp / denominatorTemp;
    }
}
 
【結果】
[テストその1]
クラス名:Divide
分子  :6
分母  :2
結果  :3

クラス名:Divide
分子  :-6
分母  :2
結果  :-3

クラス名:Divide
分子  :6
分母  :0
結果  :想定外の例外!

クラス名:Divide
分子  :null
分母  :2
結果  :0

[テストその2]
クラス名:DivideBusiness
分子  :6
分母  :2
結果  :3

クラス名:DivideBusiness
分子  :-6
分母  :2
結果  :0

クラス名:DivideBusiness
分子  :6
分母  :0
結果  :6

クラス名:DivideBusiness
分子  :null
分母  :2
結果  :0

[テストその3]
クラス名:DivideBad1
分子  :6
分母  :2
結果  :3

クラス名:DivideBad1
分子  :-6
分母  :2
結果  :想定外の例外!

クラス名:DivideBad1
分子  :6
分母  :0
結果  :想定外の例外!

クラス名:DivideBad1
分子  :null
分母  :2
結果  :0

[テストその4]
クラス名:DivideBad2
分子  :6
分母  :2
結果  :3

クラス名:DivideBad2
分子  :-6
分母  :2
結果  :-3

クラス名:DivideBad2
分子  :6
分母  :0
結果  :想定外の例外!

クラス名:DivideBad2
分子  :null
分母  :2
結果  :想定外の値!

単一責任の原則とは

オブジェクト指向の設計ではいくつかの原則があります。
その中でも有名な原則の一つが「単一責任の原則」です。
 
この原則は、その名の通り「一つのクラスに持たせる責任は一つにする」という原則です。
この原則が何のために用いられるのかと言うと、複数の理由で1つのクラスを修正するのを防ぐためです。
ここで言う「理由」を「案件」、「クラス」を「ソース」と捉えると分かりやすいと思います。
複数の案件で1つのソースを修正する必要が出てしまうと、並行開発やバージョン管理等が煩雑になってしまい、品質に悪影響が及びます。
 
「一つのクラスに持たせる責任は一つにする」と捉えると具体的なイメージが浮かびにくいと思うのですが、「複数の案件で1つのソースを修正しないような粒度で設計する」と捉えると分かりやすいと思います。
 
-----------------
 
以下では、javaソースコードで例を出します。
商品の名前と値段を登録し、それを表示するという例です。
 
まずは悪い例からです。
 
【サンプルコード1】
・CommodityMain.java
public class CommodityMain {

    public static void main(String[ ] args) {

        RegistShowCommodity registShowCommodity = new RegistShowCommodity();
        String commodityName = "PC";

        // 商品の登録
        registShowCommodity.setCommodityPrice(commodityName, 100000);

        // 商品の表示
        registShowCommodity.showCommodityPrice(commodityName);

    }

}
 
・RegistShowCommodity.java
import java.util.HashMap;
import java.util.Map;

public class RegistShowCommodity {

    private Map<String, Integer> commodityMap
        = new HashMap<String, Integer>();

    public void setCommodityPrice (String commodityName,int price) {
        commodityMap.put(commodityName,price);
    }

    public void showCommodityPrice (String commodityName) {
        System.out.println(commodityName + ":" +
            commodityMap.get(commodityName) + " yen");
    }

}
 
【実行結果1】
PC:100000 yen
 
-----------------
 
ここで、「商品の税込み価格を計算可能にする」という案件と、「表示を綺麗にする」という案件が発生したとします。
今の設計だと、どちらの案件も RegistShowCommodityクラスの修正となってしまいます。
 
そこで、商品の登録を行う RegistCommodityクラス、商品の価格計算を行う CalcCommodityクラス、価格表示を行う ShowCommodityクラスの3クラスに分割します。
 
【サンプルコード2】
・CommodityMain.java
public class CommodityMain {

    public static void main(String[ ] args) {

        RegistCommodity registCommodity = new RegistCommodity();
        CalcCommodity calcCommodity = new CalcCommodity();
        ShowCommodity showCommodity = new ShowCommodity();
        String commodityName = "PC";

        // 商品の登録
        registCommodity.setCommodityPrice(commodityName, 100000);

        // 商品の表示
        showCommodity.showCommodityPrice(commodityName,
            calcCommodity.calcPrice(
                registCommodity.getCommodityPrice(commodityName)));

    }

}
 
・RegistCommodity.java
import java.util.HashMap;
import java.util.Map;

public class RegistCommodity {

    private Map<String, Integer> commodityMap
        = new HashMap<String, Integer>();

    public void setCommodityPrice (String commodityName,int price) {
        commodityMap.put(commodityName,price);
    }

    public int getCommodityPrice (String commodityName) {
        return commodityMap.get(commodityName);
    }

}
 
・CalcCommodity.java
public class CalcCommodity {

    public int calcPrice (int price) {
        return price;
    }

}
 
・ShowCommodity.java
public class ShowCommodity {

    public void showCommodityPrice (String commodityName,int price) {
        System.out.println(commodityName + ":" +
            price + " yen");
    }

}
 
【実行結果2】
PC:100000 yen
 
-----------------
 
3クラスに分割した結果、「商品の税込み価格を計算可能にする」という案件は CalcCommodityクラスの修正、「表示を綺麗にする」という案件は ShowCommodityクラスの修正で対応できるようになり、別々の案件で1つのクラスを修正する状態を防ぐことができました。
 
【サンプルコード3】
・CommodityMain.java
public class CommodityMain {

    public static void main(String[ ] args) {

        RegistCommodity registCommodity = new RegistCommodity();
        CalcCommodity calcCommodity = new CalcCommodity();
        ShowCommodity showCommodity = new ShowCommodity();
        String commodityName = "PC";

        // 商品の登録
        registCommodity.setCommodityPrice(commodityName, 100000);

        // 商品の表示
        showCommodity.showCommodityPrice(commodityName,
            calcCommodity.calcPriceWithTax(
                registCommodity.getCommodityPrice(commodityName)));

    }
}
 
・RegistCommodity.java
import java.util.HashMap;
import java.util.Map;

public class RegistCommodity {

    private Map<String, Integer> commodityMap
        = new HashMap<String, Integer>();

    public void setCommodityPrice (String commodityName,int price) {
        commodityMap.put(commodityName,price);
    }

    public int getCommodityPrice (String commodityName) {
        return commodityMap.get(commodityName);
    }

}
 
・CalcCommodity.java
public class CalcCommodity {

    private double taxRate = 1.1;

    public int calcPrice (int price) {
        return price;
    }

    // 「商品の税込み価格を計算可能にする」案件
    public int calcPriceWithTax (int price) {
        return (int) (price * taxRate);
    }

}
 
・ShowCommodity.java
public class ShowCommodity {

    // 「表示を綺麗にする」案件
    public void showCommodityPrice (String commodityName,int price) {
        System.out.println("■CommodityName :" + commodityName);
        System.out.println("■CommodityPrice:" + price + " yen");
    }

}
 
【実行結果3】
■CommodityName :PC
■CommodityPrice:110000 yen

C#:セマフォを用いた排他制御

排他制御の仕組みとして先日Mutexを取り上げました(https://akira2kun.hatenablog.com/entry/2020/04/12/170559)。
今回は、同じく排他制御で使われるセマフォについて取り上げます。
 
セマフォがMutexと異なる点は、複数のプロセス・スレッドが資源を取得することができることです。
セマフォのコンストラクタで初期で解放する資源数や、解放できる資源の最大数を指定します。
WaitOne関数で資源取得待ちを行い、Release関数で資源解放を行います。Release関数の引数で資源解放数を指定することもできます。
(ただし、最大数を超える資源を開放するとSemaphoreFullExceptionとなるので注意が必要です)
 
セマフォは、同時に動くスレッド数を制限したいような重い処理がある時によく使われます。
 
以下は、セマフォを用いて同時に動くスレッド数を制限するサンプルです。
 
【サンプルコード】
・Controller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace Controller
{
    internal class Controller
    {
        public static void Main(string args)
        {
            using(Semaphore sem = new Semaphore(0, 2, "semaphore"))
            {
                Console.WriteLine(DateTime.Now +
                    ":セマフォを作りました。" +
                    "初期で解放0個、最大で2つまで解放します。");
                Thread.Sleep(2000);
                sem.Release(2);
                Console.WriteLine(DateTime.Now +
                    ":準備ができたので、セマフォを2つ解放しました。");
                Thread.Sleep(10000);
            }
            Console.ReadLine();
        }
    }
}
 
・Processor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace Processor
{
    internal class Processor
    {
        public static void Main(string
args)
        {
            const int N = 3;
            Parallel.For(0, N, id => // id0~2のスレッドを生成
            {
                Semaphore sem;
                if (Semaphore.TryOpenExisting("semaphore", out sem))
                {
                    Console.WriteLine("ID" + id + ":" + DateTime.Now +
                        ":セマフォ解放待ち。");
                    sem.WaitOne();
                    Console.WriteLine("ID" + id + ":" + DateTime.Now +
                        ":セマフォ取得しました。処理開始します。");
                    Thread.Sleep(2000);
                    Console.WriteLine("ID" + id + ":" + DateTime.Now +
                        ":処理終了しました。");
                    sem.Release();
                    Console.WriteLine("ID" + id + ":" + DateTime.Now +
                        ":処理終了したので、セマフォを1つ解放しました。");
                }
                else
                {
                    Console.WriteLine("ID" + id + ":" + DateTime.Now +
                        ":セマフォが見つかりません。");
                }
            });
            Console.ReadLine();
        }
    }
}
 
【実行用バッチ】
・test.bat
@echo off
echo 最大で2つのスレッドしか動かないようにするサンプルです。
echo 同時に動くスレッド数を制限したい重い処理がある時に使えます。
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Controller.cs
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Processor.cs
start Controller.exe
timeout 1
start Processor.exe
pause
exit
 
【実行結果】
・test.bat
最大で2つのスレッドしか動かないようにするサンプルです。
同時に動くスレッド数を制限したい重い処理がある時に使えます。
Microsoft (R) Visual C# Compiler version 4.7.3062.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, w
hich is no longer the latest version. For compilers that support newer versions of the C# programming language, see http
://go.microsoft.com/fwlink/?LinkID=533240

Microsoft (R) Visual C# Compiler version 4.7.3062.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, w
hich is no longer the latest version. For compilers that support newer versions of the C# programming language, see http
://go.microsoft.com/fwlink/?LinkID=533240


0 秒待っています。続行するには何かキーを押してください ...
続行するには何かキーを押してください . . .
 
・Controller.exe
2020/05/09 13:48:45:セマフォを作りました。初期で解放0個、最大で2つまで解放します。
2020/05/09 13:48:47:準備ができたので、セマフォを2つ解放しました。
 
・Processor.exe
ID0:2020/05/09 13:48:46:セマフォ解放待ち。
ID2:2020/05/09 13:48:46:セマフォ解放待ち。
ID1:2020/05/09 13:48:46:セマフォ解放待ち。
ID0:2020/05/09 13:48:47:セマフォ取得しました。処理開始します。
ID2:2020/05/09 13:48:47:セマフォ取得しました。処理開始します。
ID2:2020/05/09 13:48:49:処理終了しました。
ID0:2020/05/09 13:48:49:処理終了しました。
ID0:2020/05/09 13:48:49:処理終了したので、セマフォを1つ解放しました。
ID2:2020/05/09 13:48:49:処理終了したので、セマフォを1つ解放しました。
ID1:2020/05/09 13:48:49:セマフォ取得しました。処理開始します。
ID1:2020/05/09 13:48:51:処理終了しました。
ID1:2020/05/09 13:48:51:処理終了したので、セマフォを1つ解放しました。

C言語にはないC++独自の文法の簡単な列挙

C++C言語を拡張して開発された言語であり、C++ではオブジェクト指向をサポートする文法が追加されています。
基本的にC言語で使用していた文法はC++でも使用できるので、学習という面で見るとC言語を扱える方であればC++で追加された文法を覚えればC++も扱えるようになります。
 
今回の記事では、C++で追加された文法の中から、主なものを簡単に列挙していきます。
 
なお、javaC#といったオブジェクト指向言語も扱えるのであれば、オブジェクト指向の概念は理解しているはずなので、追加された文法の理解も早いと思います。
java基本情報技術者試験で出題される言語、C#C++と同じく.NET Frameworkでサポートされる言語であり、C++を学ぼうとする方はjavaC#についてある程度の知識があることが多いと思うので、javaC#とも簡単に対比します。
 
・クラスの概念の追加
 C++では、"class クラス名"でクラスを定義することができます。
 javaC#ではお馴染みの、
  アクセス識別子
  コンストラク
  継承
  抽象クラス
  static
 といった機能が使用できます。
 C#で実装されているデストラクタ(オブジェクト破棄時の後処理)
 もC++で使用可能です。
 
 javaC#とは記述方法が若干異なりますが、
 javaC#の経験があれば戸惑うことは少ないと思います。
 ただし、newで確保した領域を意図的にdeleteで破棄する必要がある、
 ということには注意する必要があります。
 忘れるとメモリリークになります。
 (javaC#のようなガベージコレクションの仕組みは無い)
 newはC言語で言うmalloc、deleteはC言語で言うfreeに似ていますが、
 new/deleteの場合はコンストラクタ/デストラクタが呼ばれるという違いがあります。
 
 なお、インターフェースの機能は使用できませんが、
 書き方次第でインターフェースに近いことはできます。
 
・参照の概念の追加
 クラスの概念とも関連があるのですが、
 javaC#ではお馴染みの参照型変数の概念が追加されています。
 
 参照型変数の中身はポインタで、
 C言語ではお馴染みのポインタ型変数とその点では同じなのですが、
 参照型変数の場合は指し示すアドレスを自由に変更できない
 (インクリメント等ができない)という違いがあります。
 この違いにより、ポインタ型変数で犯しがちなミスを減らす効果があります。
 
・スコープ解決演算子の追加
 C++では、変数名やメソッド名の衝突を避けるため、
 スコープ解決演算子(::)を使用します。
 
 例えば、"std::cout"と書いた場合、
 stdという名前空間(クラス名)の
 cout(main関数実行時にシステムにより生成されるオブジェクト名(変数名))
 という意味になります。
 また、外部の自作クラスにアクセスするような場合も、
 このスコープ解決演算子を使用します。
 
 なお、スコープ解決演算子は、
 ソースコードの冒頭で using namespace 名前空間;
 のようにデフォルトの名前空間を指定することで省略可能です。
 
・ストリームの概念の追加
 "<<"で出力ストリーム、">>"で入力ストリーム、という意味となります。
 ("<<"はostreamクラス、">>"はistreamクラスから派生させることで使用できます)
 
 例えば、
 "std::cout << "Hello World!\n";"
 と書けば標準出力に"Hello World!"と出力できますし、
 "std::cin >> hoge;"
 と書けば標準入力を変数"hoge"に渡すことができます。
 
・bool型の追加
 意外にもC言語にはtrue/falseを保持するbool型が用意されていません。
 C++には、java(boolean型)やC#のようにbool型が用意されています。
 
・関数のオーバーロードの追加
 同じ名前の関数でも、関数の引数が異なれば複数定義できます。
 これはjavaC#ではお馴染みの機能です。
 
・例外制御の追加
 C++ではtry~catch構文が用意されています。
 throwで意図的に例外を投げることもできます。
 これもjavaC#ではお馴染みの機能です。
 ただし、finallyは用意されていません。
 finally句で行うようなリソースの解放を行いたい場合は、
 前述のデストラクタや、
 デストラクタで自動的に領域を解放するオブジェクト(スマートポインタ)
 を利用する必要があります。

C#:共有メモリで可変長データを繰り返し送受信する

共有メモリは、同一メモリ上で実行されるプロセス間でデータをやりとりする場合に使用する仕組みです。
通常、プロセスで確保しているメモリは他のプロセスから参照することができないのですが、プロセス間で予め共有メモリとして使用するメモリのアドレスを共有することで、そのメモリは他のプロセスから参照可能となります。
ファイル等を介したやりとりよりも高速なため、高速化が求められる時に使用することが多いです。
 
目的の一つが高速化のため、データが作成され次第次々と共有する、という使い方になることが多いと思います。
今回は、そのような使い方を想定して、可変長データを繰り返し送受信するサンプルプログラムを作成しました。
書き込んだデータのサイズを伝えること、共有メモリの読み取り位置をずらしていくことがポイントとなります。
 
なお、今回は省略していますが、本来であれば書き込み中の読み取りを防ぐため、Mutex等の排他制御の仕組みを併用するべきです。
(参考までに、Mutexについては過去に記事(https://akira2kun.hatenablog.com/entry/2020/04/12/170559)を書いています)
 
【サンプルコード】
・Sender.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.MemoryMappedFiles;

namespace Sender
{
    class Sender
    {
        // 本当はMutex等で排他制御するべき
        static void Main(string[ ] args)
        {
            // 共有メモリの作成("HelloWorld"という名前で1024バイトで定義)
            MemoryMappedFile mmf =
                MemoryMappedFile.CreateNew("HelloWorld", 1024);
           
            // 書き込みオブジェクトの生成
            MemoryMappedViewAccessor mmva = mmf.CreateViewAccessor();

            // 書き込みオブジェクトの現在のバイト位置
            int i = 0;

            // 書き込み1回目
            // 共有メモリの状態
            // |int(オブジェクトの数)|char(Hello )|
            string str1 = "Hello ";
            char
data1 = str1.ToCharArray();
            mmva.Write(i, data1.Length);
            i = i + sizeof(int);
            mmva.WriteArray<char>(i, data1, 0, data1.Length);
            i = i + sizeof(char) * data1.Length;
            System.Threading.Thread.Sleep(1000);

            // 書き込み2回目
            // 共有メモリの状態
            // |(1回目の内容)|int(オブジェクトの数)|char(World)|
            string str2 = "World";
            char
data2 = str2.ToCharArray();
            mmva.Write(i, data2.Length);
            i = i + sizeof(int);
            mmva.WriteArray<char>(i, data2, 0, data2.Length);
            i = i + sizeof(char) * data2.Length;
            System.Threading.Thread.Sleep(1000);

            // 書き込み3回目
            // 共有メモリの状態
            // |(1~2回目の内容)|int(オブジェクトの数)|char(!)|
            string str3 = "!";
            char
data3 = str3.ToCharArray();
            mmva.Write(i, data3.Length);
            i = i + sizeof(int);
            mmva.WriteArray<char>(i, data3, 0, data3.Length);
            i = i + sizeof(char) * data3.Length;
            System.Threading.Thread.Sleep(1000);

            // 書き込みオブジェクトの破棄
            mmva.Dispose();

            // 共有メモリの破棄
            mmf.Dispose();
        }
    }
}

・Receiver.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.MemoryMappedFiles;

namespace Receiver
{
    class Receiver
    {
        // 本当はMutex等で排他制御するべき
        static void Main(string[ ] args)
        {
            // 作成済み共有メモリ("HelloWorld")のオブジェクト生成
            MemoryMappedFile mmf =
                MemoryMappedFile.OpenExisting("HelloWorld");
           
            // 読み込みオブジェクトの生成
            MemoryMappedViewAccessor mmva = mmf.CreateViewAccessor();

            // 読み込みオブジェクトの現在のバイト位置
            int i = 0;

            // 読み込み
            // オブジェクトの数が書き込まれていたらその分データを読む
            int size = 0;
            string str = "";
            while (!str.Equals("!"))
            {
                size = mmva.ReadInt32(i);
                if (size != 0)
                {
                    char[] data = new char[size];
                    i = i + sizeof(int);
                    mmva.ReadArray<char>(i, data, 0, data.Length);
                    str = new string(data);
                    Console.WriteLine(str);
                    i = i + sizeof(char) * data.Length;
                }
                System.Threading.Thread.Sleep(100);
            }
           
            // 読み込みオブジェクトの破棄
            mmva.Dispose();
           
            // コンソール表示を維持
            Console.ReadKey();
        }
    }
}

【実行バッチ】
・test.bat
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Sender.cs
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Receiver.cs
start Sender.exe
timeout 1
start Receiver.exe
pause
exit

【実行結果】
・Receiverのコンソール
Hello
World
!

WPFでSelectedItemが同期されない→ItemsSourceを先に設定してしまっているからかも

表題の通りです。
 
XAMLで定義した画面上のリストで特定の行を選択した際、選択した行と結びついているオブジェクトを取得できないことがありハマっていたのですが、SelectedItemを先に定義してからItemsSourceを定義するようにしたら上手く取得できるようになりました。
 
こちらのページの「解決した方法 # 3」を参考にしました。
 
c# - ComboBox SelectedItemバインディングが更新されない - プログラミングQ&A - BugInfo

https://buginfo.tech/questions-1202798.htm

 
冗談のような解決法ですが、設定順によってXAMLから生成されるコードのロジックが変わるのかもしれません。

半角カナの文字コードに関する注意

0x00~0x7FのASCIIコード(半角英数記号やNUL・改行等の特殊な文字)に関しては文字コードセットの違いを気にする必要がないのですが、日本語の文字に関しては複数の文字コードセットが存在し、違いを気にする必要があります。
半角カナについても例外ではなく、同じ半角文字といっても半角英数記号とは扱いが異なります。1バイトであるとも限りません。
 
例えば、SJISでは0xA1~0xDFが半角カナの領域なのですが、Unicode系では下記公式ドキュメントの通り0xFF61~0xFF9Fが半角カナの領域となっています。

https://www.unicode.org/charts/PDF/UFF00.pdf

 
Webで調べてみても、半角カナの扱いの違いによる互換性の問題が散見されます。文字コードを判定したり変換したりする際には注意が必要です。
 
なお、C#のプログラムで引数で半角カナを与えた場合、下記の通りUnicode系の文字コードが割り振られるようです。
(引数を与えているバッチファイルはSJISで記述しています)
 
【サンプルコード】
・Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            string str = args[0];
            ushort code;
            for(int i = 0; i < str.Length; i++)
            {
                code = str[i];
                Console.WriteLine
                    (str[i] + " : " + Convert.ToString(code, 16));
            }
        }
    }
}
 
・Program.bat
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Program.cs
Program.exe 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚
pause
 
【実行結果】
C:\tmp>C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Program.cs
Microsoft (R) Visual C# Compiler version 4.7.3062.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, w
hich is no longer the latest version. For compilers that support newer versions of the C# programming language, see http
://go.microsoft.com/fwlink/?LinkID=533240


C:\tmp>Program.exe 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚
。 : ff61
「 : ff62
」 : ff63
、 : ff64
・ : ff65
ヲ : ff66
ァ : ff67
ィ : ff68
ゥ : ff69
ェ : ff6a
ォ : ff6b
ャ : ff6c
ュ : ff6d
ョ : ff6e
ッ : ff6f
ー : ff70
ア : ff71
イ : ff72
ウ : ff73
エ : ff74
オ : ff75
カ : ff76
キ : ff77
ク : ff78
ケ : ff79
コ : ff7a
サ : ff7b
シ : ff7c
ス : ff7d
セ : ff7e
ソ : ff7f
タ : ff80
チ : ff81
ツ : ff82
テ : ff83
ト : ff84
ナ : ff85
ニ : ff86
ヌ : ff87
ネ : ff88
ノ : ff89
ハ : ff8a
ヒ : ff8b
フ : ff8c
ヘ : ff8d
ホ : ff8e
マ : ff8f
ミ : ff90
ム : ff91
メ : ff92
モ : ff93
ヤ : ff94
ユ : ff95
ヨ : ff96
ラ : ff97
リ : ff98
ル : ff99
レ : ff9a
ロ : ff9b
ワ : ff9c
ン : ff9d
゙ : ff9e
゚ : ff9f

C:\tmp>pause
続行するには何かキーを押してください . . .