プログラマになる

組み込みエンジニアで主にC言語、時々C#をやっている人の技術メモです。最近はAndroidも少しずつ勉強中

MacOSにCppUTestをインストールする

 

はじめに

本記事はC/C++用のxUnit系テストフレームワークである
CppUTestをMacOSにインストールする備忘録です。
CppUTestについてはこちらに詳細が記載されているので省略します。

インストール

CppUTestをインストールするための手順を次に記載します。

1. 次のコマンドでCppUTestをインストールする。

コマンド

> brew install cpputest

2. 次のエラーが出たので、次のコマンドを実行する。

エラー

Error: The following directories are not writable by your user:
/usr/local/include

You should change the ownership of these directories to your user.
  sudo chown -R $(whoami) /usr/local/include

コマンド

> sudo chown -R $(whoami) /usr/local/include

3. 次のコマンドでCppUTestをインストールしたパスを確認する。

コマンド

❯ brew --prefix cpputest
/usr/local/Cellar/cpputest/3.8

サンプル

CppUTestを使った最も簡単サンプルを次に記載します。

1. テストを記述する

test_runner.cpp

#include "CppUTest/CommandLineTestRunner.h"
#include "CppUTest/TestHarness.h"

TEST_GROUP(FirstTestGroup)
{
};

TEST(FirstTestGroup, FirstTest)
{
    STRCMP_EQUAL("first", "first")
}

TEST(FirstTestGroup, SecondTest)
{
    STRCMP_EQUAL("second", "second")
}

int main(int ac, char** av)
{
   return CommandLineTestRunner::RunAllTests(ac, av);
}

2. Makefileを記述する

makefile

CPP          = g++ -std=c++11
CFLAGS       =
INCLUDE      =
LIBPATH      =
LIBS         =
SOURCE       = test_runner.cpp
PROGRAM      = test_runner

CPPUTEST_HOME = /usr/local/Cellar/cpputest/3.8
INCLUDE  += -I$(CPPUTEST_HOME)/include/CppUTest/
INCLUDE  += -I$(CPPUTEST_HOME)/include/CppUTestExt/
LIBPATH  += -L$(CPPUTEST_HOME)/lib
LIBS     += -lCppUTest -lCppUTestExt -lpthread

ALL: 
    $(CPP) $(CFLAGS) $(INCLUDE) $(LIBS) $(SOURCE) -o $(PROGRAM)

clean:
    rm $(PROGRAM)

3. makeして、テストプログラムを実行する

❯ make
❯ ./test_runner
..
OK (2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 0 ms)

おわりに

これでとりあえずCPPUTestが使えるようになりました。
あとはリファレンス見ながらTest MacrosやらAssertionを学ぶ必要があると思う。
とりあえず本記事ではここまで…

Kotlinで内部クラスを理解する

はじめに

Kotlin in Actionを読み進めているとき内部クラスが全く理解できませんでした
なので今回は内部クラスについて調べたこと実践したことをまとめてみたいと思います。

調べる

クラス内に宣言されたクラスを内部クラスと呼ぶらしいです。
これだけ説明されると普段やるクラス内にクラスを定義するのと何が違うのかという疑問が出ると思います。
以下のコードと何が違うんだろうかと、筆者がクラス内に宣言されたクラスと説明されたとき思いました。

クラスの中にクラスを定義

class ClassA {
  class ClassB() { 
    ...
  }
}

疑問を解決するために調べてみたところ、
内部クラス内部クラスにとっての外部クラスのインスタンス(エンクロージングインスタンス)にアクセスできるらしい。
以下のコードはあくまでもイメージですが、ClassBからClassAにアクセスような感じでしょうか?

class ClassA {
  val id = "ClassAですよ"
  class ClassB() { 
    fun getOuterId() : String = ClassA.id
  }
}

はっきり言って説明されてもよくわからないので実際にコードを書いて確認してみます。

実践する

Kotlinではクラス定義にInner識別子をつけることで内部クラスを定義できます。
なので外部クラスとしてOuterクラス、内部クラスとしてInnerクラスを定義しました。

class Outer {
    inner class Inner {
     
    }
}

次に内部クラスの特徴であるエンクロージングインスタンスへアクセスするための仕組みを実装します。
内部クラスからエンクロージングインスタンスへはthis@クラス名.プロパティ名でアクセスできるようです。
なのでOuterクラスにIDプロパティ、InnerクラスにGetOuterIdにてthis@Outer.idでアクセスします。

class Outer {
    val id = "アウターですよ"
    inner class Inner {
      fun getOuterId() : String = this@Outer.id
    }
}

後はInnerクラスのインスタンスを生成してエンクロージングインスタンスへアクセスするのみです。
Innerクラスのインスタンスを生成するにはエンクロージングインスタンスが必要になるので
エンクロージングエンスタンスを生成したあとにInnerインスタンスを生成してIDプロパティを出力します。

fun main(args: Array<String>) {
    val enclosing_instance = Outer()
    val inner = enclosing_instance.Inner()
    println(inner.getOuterId())
}

Output
------------------------------------------
アウターですよ
Process finished with exit code 0
------------------------------------------

無事、エンクロージングインスタンスにアクセスすることができました。

おわりに

調べたこと実践したことをまとめます。

  • 内部クラスを作るときはClassにinner識別子をつける。
  • 内部クラスを定義すると内部クラスのインスタンスからエンクロージングインスタンスにアクセスすることができる。
  • 内部クラスのインスタンスからthis@クラス名.プロパティ名でエンクロージングクラスにアクセスできる。
  • 内部クラスを生成するときにはエンクロージングクラスが必要になる

参考

以下の書籍・記事を参考にしながら本記事を作成しました。

DockerでArchLinux環境を構築する

はじめに

下記の環境でArchLinuxを構築してみます。

Dockerをインストール

まずはDockerがないことには始まりませんので、
ここからダウンロードしインストールします。
macOSだとドラッグ&ドロップでインストールが開始できるので後は流れに沿ってインストールしてください。

f:id:kaleidot725:20180729153857p:plain

Dockerにログイン

インストール時にユーザアカウントの作成しログインを実行しますが、別途コマンドからログインする必要があるみたいです。
ArchLinuxをインストールする前に下記のコマンドを実行しログインします。
実行しますとユーザ名とパスワードが尋ねられますのでDocker登録時に設定した値を入力してください。

$ docker login
Username (XXX.XXX): <ユーザ名>
Password: <パスワード>

ArchLinuxをインストール

Dockerの準備が整ったところでArchLinuxをインストールします。
今回はArchLinuxJpが公開しているDockerイメージを利用します。
下記のコマンドでインストールします。

$ sudo docker pull archlinuxjp/archlinux

インストールが完了するとイメージ一覧にarchlinuxjp/archlinuxが追加されます。

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
archlinuxjp/archlinux   latest              5328ddb376e0        32 hours ago        399MB

ArchLinuxのコンテナを作成

後はインストールしたArchLinuxイメージのコンテナを作成するだけです。

$ docker run -it archlinuxjp/archlinux /bin/bash

するとこんな感じでログインできます。

f:id:kaleidot725:20180729153855p:plain

おわりに

今回はDockerを使ってArchLinux環境を構築してみました、今更ながらDockerの強力さに気づきました…
そもそも「Linuxのしくみ」という書籍を読み進めるための環境づくりでしたが良い気付きが得られたかと思います。

参考記事

Macでdockerを使って使い捨ての環境を作成する
ArchLinuxJP/docker-arch

今更ながらListViewをまとめる - ViewHolder編 -

はじめに

今更ながらListViewをまとめるの続きです。
今回はViewHolderについて説明してみたいと思います。
またPersonクラスを生成してそのプロパティをListViewとViewHolderを使って表示してみます。

1. ListViewに表示するItemを作成

ここは前回と同じくPersonクラスを定義します。

Person.kt

class Person(firstName : String, lastName : String, age : Int) {
    val firstName : String = firstName
    val lastName : String = lastName
    val age : Int = age
}

またPersonクラスを表示するためのLayoutを作成します。
今回も単純にPersonクラスを表示するものしています。

list_item.xml

f:id:kaleidot725:20180624162651p:plain

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/personItem"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:id="@+id/firstName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="firstName"
        android:background="#FF9999"/>

    <TextView
        android:id="@+id/lastName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="lastName"
        android:background="#99FF99"/>

    <TextView
        android:id="@+id/age"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="age"
        android:background="#9999FF"/>
</LinearLayout>

2.ViewHolderを使ったAdapterを作成する

list_item.xmlをViewとして生成するためにAdapterを作成します。
また今回はViewHolderを実装して、ListViewの表示速度を改善したいと思います。

なぜViewHolderが必要なのか?

まずViewHolderとはなんぞやという感じだと思いますが、
その名の通りViewを保持する役割のクラスです。
じゃあなぜViewHolderでViewを保持する必要があるのでしょうか?
ViewHolderを使わない場合のコードを確認してみましょう。

ListViewAdapter

class ListViewAdapter(fragmentActivity: FragmentActivity?, resource : Int, list: List<Person>) : ArrayAdapter<Person>(fragmentActivity, resource, list) {
    private val inflater : LayoutInflater = LayoutInflater.from(context)

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {

        val view = inflater.inflate(R.layout.list_item, parent, false)

        val firstNameTextView = view.findViewById<TextView>(R.id.firstName)
        firstNameTextView.text = super.getItem(position).firstName

        val lastNameTextView = view.findViewById<TextView>(R.id.lastName)
        lastNameTextView.text = super.getItem(position).lastName

        val ageTextView = view.findViewById<TextView>(R.id.age)
        ageTextView.text = super.getItem(position).age.toString()

        return view
    }
}

getViewが呼ばれるたびにViewを生成してfindViewByIdを使って値をセットしています。
このfindViewByIdがパフォーマンス上問題となります。
findViewByIdは呼び出しコストが高く、回数が多くなるつまりViewが増えるほどパフォーマンスが低下します。
つまりListViewを高速化したい場合はどうにかしてfindViewByIdの呼び出し回数を減らさなければならないということです。
そこでViewHolderが登場することになります。

ViewHolderAdapterを作成する

findViewByIdの呼び出し回数を減らすために新規作成時にfindViewByIdで取得した値をViewHolderに保持しておき、
二回目以降にViewを使う場合にはViewHolderに保存したViewを使うようにします。
これにてfindViewByIdを呼び出す回数が減りListViewを高速化できます。

今回はPersonクラスのプロパティを表示するTextViewを保持するViewHolderを生成します。

class ViewHolder(var firstNameTextView : TextView, var lastNameTextView : TextView, var ageTextView : TextView)

作成したViewHolderを使ってAdapterを実装します。
一回目だけ新規作成、二回目以降はViewHolderの値を利用するように実装します。

class ViewHolderAdapter(fragmentActivity: FragmentActivity?, resource : Int, list: List<Person>) : ArrayAdapter<Person>(fragmentActivity, resource, list) {
    private val inflater : LayoutInflater = LayoutInflater.from(context)

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        lateinit var convertViewTemp : View
        lateinit var viewHolder : ViewHolder

        if (convertView == null) {
            // Viewを作成
            convertViewTemp = inflater.inflate(kaleidot725.listviewlaboratory.R.layout.list_item, parent, false)
          
            // Viewから必要となるTextViewをfindViewByIdで取り出し、ViewHolderに格納しViewのタグにセット
            val firstNameTextView = convertViewTemp.findViewById<TextView>(kaleidot725.listviewlaboratory.R.id.firstName)
            val lastNameTextView = convertViewTemp.findViewById<TextView>(kaleidot725.listviewlaboratory.R.id.lastName)
            val ageTextView = convertViewTemp.findViewById<TextView>(kaleidot725.listviewlaboratory.R.id.age)
            viewHolder = ViewHolder(firstNameTextView, lastNameTextView, ageTextView)
            convertViewTemp.setTag(viewHolder)
        }
        else {
            // タグにセットしておいたViewHolderを取得し、findViewByIdを使わないでTextViewを取得する
            convertViewTemp = convertView          
            viewHolder = convertViewTemp.getTag() as ViewHolder
        }

        viewHolder.firstNameTextView.text = super.getItem(position).firstName
        viewHolder.lastNameTextView.text = super.getItem(position).lastName
        viewHolder.ageTextView.text = super.getItem(position).age.toString()
        return convertViewTemp
    }
}

3.ListViewを作成する

ここは前回と変わりません。
FramgentのLayoutにListViewを追加し表示できるようにします。

fragment_view_holder

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ViewHolder.ViewHolderFragmenrt">

<ListView
    android:id="@+id/ViewHolderList"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</ListView>
</FrameLayout>

ViewHolderFragmenrt

class ViewHolderFragmenrt : Fragment() {
    val values : List<Person> = mutableListOf(
            Person("Mike",   "Ryan", 15),
            Person("Fred",   "Griffin", 20),
            Person("Ruke",   "Mohammed", 30),
            Person("ふじい",  "まきこ", 40),
            Person("あずま",  "りさ" , 31),
            Person("りゅう",  "ふみえ" , 32),
            Person("とりうみ","のりこ" , 34),
            Person("はすみ",  "ちづる" , 46),
            Person("もりたに","かおり" , 25),
            Person("すがわら","けいすけ" , 36),
            Person("すみよし","たつや" , 40),
            Person("かつやま","まさのり" , 33),
            Person("させ",   "まさあき" , 27),
            Person("ふない",    "ゆうすけ" , 35),
            Person("じんぐうじ", "ただゆき" , 36),
            Person("かねひら",   "ともはる" , 25),
            Person("おぶち",     "こうじ" , 24),
            Person("あいやま",   "やすゆき" , 18))


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_view_holder_fragmenrt, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val viewHolderList = view.findViewById<ListView>(R.id.ViewHolderList)
        viewHolderList.adapter = ViewHolderAdapter(activity, android.R.layout.simple_expandable_list_item_1, values)
    }
}

さいごに

このぐらいの表示数ですとそこまでパフォーマンスに影響しないようです。
おそらくですが画像とかをたくさんListViewに配置すると大きな差ができそうです。

f:id:kaleidot725:20180701205642g:plain

github.com

今更ながらListViewをまとめる - 基礎編 -

はじめに

今更ながらListViewの使い方をまとめたいと思います。
今回はPersonクラスを生成してそのプロパティをListViewに表示してみます。

1. ListViewに表示するItemを作成

まずはPersonクラスを定義します。

Person.kt

class Person(firstName : String, lastName : String, age : Int) {
    val firstName : String = firstName
    val lastName : String = lastName
    val age : Int = age
}

Personクラスを表示するためのLayoutを作成します。
今回は単純にPersonクラスを表示するものしてみました。

📝 simple_list_item1など標準で用意されているものもありますが今回は用途と合わないので独自で定義します。

list_item.xml

f:id:kaleidot725:20180624162651p:plain

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/personItem"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:id="@+id/firstName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="firstName"
        android:background="#FF9999"/>

    <TextView
        android:id="@+id/lastName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="lastName"
        android:background="#99FF99"/>

    <TextView
        android:id="@+id/age"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="age"
        android:background="#9999FF"/>
</LinearLayout>

2.ListViewのAdapterを作成する

list_item.xmlをViewとして生成するためにAdapterを作成します。
以下のようにArrayAdapterを継承したListViewAdapterを作成します。

ListViewAdapter

// コンストラクタで予め、表示したいPersonのリストを指定する。
class ListViewAdapter(fragmentActivity: FragmentActivity?, resource : Int, list: List<Person>) : ArrayAdapter<Person>(fragmentActivity, resource, list) {
    private val inflater : LayoutInflater = LayoutInflater.from(context)

    // ListViewによってViewが必要なときに呼び出される、なのでViewを生成して返してあげる
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        // Viewを生成して、Adapterのコンストラクタから渡されたPersonListの要素をセットする
        val view = inflater.inflate(R.layout.list_item, parent, false)

        // 生成したViewからTextViewを取得する
        val firstNameTextView = view.findViewById<TextView>(R.id.firstName)
        val lastNameTextView = view.findViewById<TextView>(R.id.lastName)
        val ageTextView = view.findViewById<TextView>(R.id.age)

        // Personリストから値を取得し、セットする
        firstNameTextView.text = super.getItem(position).firstName
        lastNameTextView.text = super.getItem(position).lastName
        ageTextView.text = super.getItem(position).age.toString()

        return view
    }
}

3.ListViewを作成する

そしていよいよ最後です。
FramgentのLayoutにListViewを追加し表示できるようにします。

fragment_list_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ListView.ListViewFragment">

    <ListView
        android:id="@+id/ListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
</LinearLayout>

次にFragmentのsrcに表示するPersonを生成してListViewにセットします。

ListViewFragment

📝ListView.adapterにListViewAdapterを渡しておく 📝ListViewは渡されたListViewAdapterを使ってViewを生成するのでitem_list.xmlのViewがListに表示される

class ListViewFragment : Fragment() {
    // 表示するリスト生成する
    val values : List<Person> = mutableListOf(
        Person("Mike",   "Ryan", 15),      Person("Fred",   "Griffin", 20), 
        Person("Ruke",   "Mohammed", 30),  Person("ふじい",  "まきこ", 40),
        Person("あずま",  "りさ" , 31),      Person("りゅう",  "ふみえ" , 32),
        Person("とりうみ","のりこ" , 34),     Person("はすみ",  "ちづる" , 46),
        Person("もりたに","かおり" , 25),     Person("すがわら","けいすけ" , 36),
        Person("すみよし","たつや" , 40),     Person("かつやま","まさのり" , 33),
        Person("させ",   "まさあき" , 27),    Person("ふない",    "ゆうすけ" , 35),
        Person("じんぐうじ", "ただゆき" , 36), Person("かねひら",   "ともはる" , 25),
        Person("おぶち",     "こうじ" , 24),  Person("あいやま",   "やすゆき" , 18))

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_list_view, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // PersonリストをセットするListViewを取得する
        val listView = view.findViewById<ListView>(R.id.ListView)
      
        // ListViewAdapterを生成して、そのときにvaluesを引数として渡す
        listView.adapter = ListViewAdapter(activity, R.layout.list_item, values)
    }
}

さいごに

エミュレータで動かすとこんな感じに動きます。
結構スムーズに動作しているように見えますが実機で動かすとカクカクします。

ここでViewHolderを実装する必要性が出てきます。
ViewHolderはListに表示する数が多くなったときのパフォーマンスを改善する手法です。
次回はViewHolderについてまとめたいと思います。

f:id:kaleidot725:20180624163746g:plain

github.com

ViewModelを生成する際にModelを指定する

はじめに

ViewModelProvidersを通してViewModelを生成する場合、 ModelをViewModelに受け渡すには一工夫必要みたいです。 本記事ではModelをViewModelに渡すための実装方法を記載します。

実装

ViewModelProviders.createの呼び出し時にViewModelProvider.Factoryクラスを指定することで ViewModelにModelをDIすることができるようになります。

1.ViewModelを作成する

ViewModelがModelをDIできるようにコンストラクタのパラメータにModelを追加します。

class TextToSpeechDemoViewModel(tts : TextToSpeech) : ViewModel() {
    val tts : TextToSpeech = tts
}

2.ViewModelFactoryを作成する

ViewModelProviders.Factoryを継承し、ModelをViewModelにDIするように実装します。

class ViewModelFactory(tts : TextToSpeech) : ViewModelProvider.Factory{
    val tts : TextToSpeech = tts

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {

        if (modelClass == TextToSpeechDemoViewModel::class.java)
            return TextToSpeechDemoViewModel(tts) as T

        throw IllegalArgumentException("Unknown ViewModel class : ${modelClass.name}")
    }
}

3.ViewModelFactoryをViewModelProvidersにセットしViewModelを生成する

2で作成したViewModelFactoryをViewModelProviers.ofに指定しViewModelを生成します。

class TextToSpeechDemoActivity : AppCompatActivity() {
    var tts : TextToSpeech? = null
    var viewModel : TextToSpeechDemoViewModel? = null
    var viewModelFactory : ViewModelFactory? = null
    
    fun onCreate(savedInstanceState: Bundle?) {
        val tts = android.speech.tts.TextToSpeech(applicationContext, object : TextToSpeech.OnInitListener{
            override fun onInit(p0: Int) {
                if(p0 != android.speech.tts.TextToSpeech.ERROR)
                    tts?.language = Locale.JAPANESE
            }
        })

        viewModelFactory = ViewModelFactory(tts as TextToSpeech)
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(TextToSpeechDemoViewModel::class.java)
    }
}

おわりに

ViewModelProviersからViewModelを生成する場合にModelをDIする方法を載せました。
Modelが多くなった場合にはこの別の実装が必要になるかもしれませんが小規模ならばこれで十分だと思います。

参考記事

https://medium.com/@dpreussler/add-the-new-viewmodel-to-your-mvvm-36bfea86b159

C# XMLファイルの書き込みと読み込み

はじめに

C#XMLファイルの書き込み・読み込みを利用するには下記のクラスを利用する。

名称 説明
XmlWriter XML形式でファイルに書き込む
XmlTextWriter おそらく非推奨でXmlWriterを利用したほうがよい
XmlReader XML形式のファイルを読み込む

サンプルコード

上記のクラスを使ったXMLファイルの書き込みと読み込みのサンプルです。
※書き込むクラスのアクセシビリティはpublicでなければならないので注意が必要です。

public class Person
{
  public string Name {get; set;}
  public string Age { get; set; }
}

public void Save()
{

    var setting = new XmlWriterSettings();
    setting.Indent = true;

    using (var writer = XmlWriter.Create(@"app.xml", setting))
    {
        var serializer = new XmlSerializer(typeof(Person));
        var person = new Person();
        serializer.Serialize(writer, person);
    }
}

public void Load()
{
    using (var reader = XmlReader.Create(@"app.xml"))
    {
        var serializer = new XmlSerializer(typeof(Person));
        var person = serializer.Deserialize(reader);
    }
}

インデント付きで書き込むには?

XmlWriterSettings.Indentをtrueに設定し、
XmlWriterのインスタンスを生成する際に指定します。

    var setting = new XmlWriterSettings();
    setting.Indent = true;
    var writer = XmlWriter.Create(@"app.xml", setting)

コレクションを書き込むには?

内部に配列を持つコレクションクラスを作成します。

  [XmlRoot("Persons")]
  public class PersonCollection
  {
      [XmlElement(Type = typeof(Person), ElementName = "Person")]
      public Person[] Persons { get; set; }
  }
  
  var serializer = new XmlSerializer(typeof(Persons));
  var persons = new Persons();
  serializer.Serialize(writer, persons);