ごまなつ Blog

楽しく働ける世界を目指して

【C#】ヒアドキュメント(逐語的文字列リテラル:@””)の使い方

ヒアドキュメントとは、\や”などの特殊文字エスケープシーケンスを必要とせず、改行を含むことができる文字列の記述法です。C#においては、文字列でヒアドキュメントができますが、「逐語的文字列リテラル」「ヒアストリング」「@-quoted string」と呼ばれています。

ファイルパスでよく使われます。\がファイルパスに使われているからです。

記述法

文字列リテラルの前に@をつけるだけです。

通常の文字列では、\を表示するためにはエスケープシーケンスの\をつける必要があります。

string filepath = "C:\\hoge\\hogehoge"

先頭に@をつけると、この書き方で宣言できます。

string filepath = @"C:\hoge\hogehoge"

改行を含んだ文字列は

string mail = "○○様\n○○です\nお世話になっております。"

string mail = "○○様
               ○○です
               お世話になっております。"

と書けます。 改行に関しての注意点は、インデントのための空白も文字列に含まれてしまう点です。ですので、空白を取り除くメソッドを作ってそれに読ませるか、改行後の文字列を左詰めで書いてください。

改行を文字列に含むことができるのは、SQL文に役立ちます。SQL文を文字列で宣言するとき、

string sql="SELECT * FROM hogeTABLE";
sql+= "WHERE hoge =A AND hogehoge=B";
sql+="ORDER BY ASC;";

のように+=で文字列をつなげていたり、

string sql=new StringBuilder();
sql.Append("SELECT ");
sql.Append("          * ");
sql.Append( "FROM ");
sql.Append("          hogeTABLE");
sql.Append("WHERE ") ;
sql.Append("hoge =A AND hogehoge=B");
sql.Append("ORDER BY ASC;");

のようにAppendでつなげたりしますが、

string sql=@"SELECT * FROM hogeTABLE WHERE hoge =A AND hogehoge=B ORDER BY ASC;"

のように1行で書くことができます。

まとめ

文字列リテラルの前に@をつけると\や”などの特殊文字エスケープシーケンスを必要とせず、改行を含むことができる文字列になる。ファイルパスやSQL文で便利。

.NET フォームアプリ ListViewに編集不能な項目を表示したい

.NETのフォームアプリにおいて、ListViewの中に編集不能な項目を表示する必要がありました。具体的には、ListViewに表示した項目でチェックボックスが入っているものに対してのみ操作をする状況で、その中に編集不能な項目を表示する必要がありました。ListViewには、1つの項目に対するEnabledプロパティがありません。どうやって実現したかの記録です。

結論

変更イベントが起きたら、変更前の状態に強制的に設定する。

private void listView_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            if (listView.Items[0].Checked == false)
            {
                listView.Items[0].Checked = true;
            }
        }

        private void listView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (listView.Items[0].Selected == true)
            {
                listView.Items[0].Selected = false;
            }
        }

listView.Items[0]はListViewの1つ目の項目です。チェックが入っている状態をキープしたいので、チェックボックスの値が変更されたイベントが起こったときチェックが外されているとチェックを入れます。 選択された状態にもならないように、選択項目が変更されたイベントが起こったとき選択された状態だと選択を外します。

無理やりではありますが、単純に考えるとこの方法が分かりやすいですし直感的だと思います。

2日連続マウスについて登壇してきました

マウスについて2日連続の勉強会で登壇してきました。「鍵盤のお伴に注目する」は時間の関係で短めに、「あなたの知らないマウスの世界」は私がマウスについて話したかったことの完全版です。

こちらは参加者の方々に、前に立って話しました。 willgate.connpass.com

マウスの持ち方と自分の好きなマウス・マウスパッドを紹介する内容でした。マウスの持ち方については知らなかったとの声があり、紹介してよかったなと思いました。前に立って話すとやはり緊張しますね……。

反省点

  • 自己紹介や前置きに時間をかけすぎたことで、終盤がほぼ話せませんでした。こだわりのマウス・マウスパッドの話をしたかったのですが……。
  • 話したいことは多くあるので、時間内に収まるように話さない内容を決めたり、時間をかけない部分を予め決めておくようにしようと思いました。

こちらは参加者が少人数だったこともあり、一つの机で座談会のようにして行いました。 supporterzcolab.com

こちらは、マウスの話の完全版です。マウスの種類、持ち方、選び方、マウスパッドの要素、リストレストの注意点について話しました。少人数だったため適宜質問が出てフィードバックがあり、精神的な面でかなりやりやすかったです。

反省点

  • 和気あいあいと進めすぎて、話が脱線した
  • 質問をする人が固定化していて、それ以外の人が質問しにくくなっていた

なぜマウスについて登壇しようと思ったのか

デスクワーカーでPCを使う人は、キーボードかマウスを必ず触っています。長く触れている椅子にはこだわる方が多いのに、キーボードとマウスにこだわる方は少ないです。最近では、エンジニアを中心にキーボード界隈が盛り上がっています。キーボードにこだわる方は増えましたが、マウスにこだわる方は少ないです。どちらもこだわったほうが快適なデスクワークができるので、マウスにこだわってみませんか?という提案をしたかったからです。

最後に

マウスとキーボードにこだわる話をしたので、次はモニターかな?

登壇は1回目の障壁が大きいですが、ぜひやってみてください。1回登壇すると、障壁はかなり低くなります。技術的でない登壇ができる勉強会は意外とあります。登壇に対する心理的な抵抗をなくせば、技術的なネタができれば技術的な登壇もしていけます。自分の知見を知らない人に伝えて、自分が知らない知見を教えてもらいましょう!

「エンジニアのためのコーチング入門」参加レポート

6/11に開催された、「エンジニアのためのコーチング入門」に参加してきました。登壇資料はイベントページにすべて挙がっています。

coaching.connpass.com

導入スライド

以下は私のメモです。詳しくは登壇資料をご覧ください。理解が深まります。どのスライドも考えさせられるので、ぜひ読んでいただきたいです。

人と話すことが苦手な人の方が、コーチングは上手くなる / 三橋 新

  • 自分がやれることは、経験によって増えるが、何でも屋になってしまうと他人の人生を生きることになり、承認欲求の限界が来る
  • 全ての人の期待に応えることはできないので、一部の人に最高の価値を提供するマインドセットに変える
  • 自分がやれることをそぎ落として、好きで得意で人のためになることにフォーカスすると誰でも、何者かになれる。自分の強みは、人に聞こう。
  • 人生の目的は、言語化できていなくてもよい。イメージでもいい。あなたの目が死なないように
  • 「コミュニケーションは上手に話さなくてはならない」は囚われ。コーチングは自分のことを話さなくていい
  • コーチングは相手を主体とした協働作業。好奇心、問い、おうむ返し
  • コーチングで一番大切なのは相手の可能性を信じること

1on1で使えるコーチングスキルの活かし方 / 谷内 真裕

  • 1on1でありがちな囚われは、相手を自分が導かなければならないという考え
  • 相手に話すと、自分にも響くが相手に焦点を当てる
  • 隠しているつもりでも、ウソ・ごまかし・恐れが伝わってしまう。自分が完璧である必要はない
  • 先に自分が相手を信じる
  • 導入セッションをする。目的や話すこと、約束を文書化して事前に読んでもらう
  • 1on1のスキルはいろいろあるが、すべては傾聴から始まる
  • 傾聴の練習が大事
  • 相手が発するエネルギーの強弱はとても重要

エンジニアリングマネージャー育成におけるコーチング / 安西 剛

  • オーセンティックであることが重要。自分らしさを貫いていること。
  • VUCAの時代では自分で答えに近いものを出すしかない。思い込みを手放す
  • 自己認識。自分と向き合い、フィードバックを受け入れる
  • 自分の弱さを認め、そんな自分をありのまま認めているか?
  • 自分の弱さを他人に話せているか?
  • 強いリーダーでいるよりオーセンティックである方が勇気が必要
  • オーセンティックであると、覚悟・一貫性を持ち、学び続けることができる
  • リーダー像の答えは人によって違い、絶対的なものは存在しない。思い込みを外し、探求し続ける。人間として成長し続ける
  • オーセンティックであり、フィードバックをすることがエンジニアリングマネージャーに求められる

感想

まず、登壇者の皆さんは話し方がとてもうまかったです。聞き取れないことが一度もありませんでした。声が大きすぎず小さすぎず、速すぎず遅すぎず、ちょうどよかったです。 うまくいかないときに、どうしても手法のせいにしてしまうことはありがちだと思います。また、あり方が重要だと気づけていなかったこともあるあるだと思います。あり方についての情報が今までなかったですが、この会に参加することでとても多くのあり方のヒントを得ることができました。答えは人によって異なるしVUCAな時代で答えはわからないですが、ヒントを使って答えに近いものを見つけて実践していきたいと思います。

【.NET Windows フォームアプリ】 ListViewの文字列と比較したい

ListViewの文字列を、定義した文字列 と比較したかったのですがうまくいかなかった記録です。

結論

listView.Items[i].ToString()ではなく、listView.Items[i].Textを使う。listView.Items[i].ToString()でとれるものは、string型とは異なるもの。 listView.Items[i].Textはstring型でとれるので、string型と比較できる。

過程

最初は、 listView.Items[i].ToString()を使いました。とれるものは "ListViewItem: {内容}" 種類:stringでした。ここで、s="内容"として比較すると、必ずfalseになります。ListViewItem:がついているからだと思い、substringを使ってListViewItem:を文字列から消去しました。s="{内容}"として2つを比較しました。結果は、必ずfalseになります。

ここで、listViewの文字列を取り出すにはlistView.Items[i].Textが使えることを発見し、比較がうまくいったという形です。

最後に

公式リファレンスを読む癖をつけたいと思います。

Goで簡単なWebサーバを作る(net/http)

Goでnet/httpを用いて簡単なWebサーバを作ります。

package main

import (
    "html/template"
    "io/ioutil"
    "log"
    "net/http"
    "regexp"
)

type Page struct {
    Title string
    Body  []byte
}

func (p *Page) save() error {
    filename := p.Title + ".txt"
    return ioutil.WriteFile(filename, p.Body, 0600)
}

func loadPage(title string) (*Page, error) {
    filename := title + ".txt"
    body, err := ioutil.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    return &Page{Title: title, Body: body}, nil
}

var templates = template.Must(template.ParseFiles("edit.html", "view.html"))

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    err := templates.ExecuteTemplate(w, tmpl+".html", p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    err := p.save()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        m := validPath.FindStringSubmatch(r.URL.Path)
        if m == nil {
            http.NotFound(w, r)
            return
        }
        fn(w, r, m[2])
    }
}
func main() {
    http.HandleFunc("/view/", makeHandler(viewHandler))
    http.HandleFunc("/edit/", makeHandler(editHandler))
    http.HandleFunc("/save/", makeHandler(saveHandler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

TitleとBodyを持つPage structを作り、そこにloadPageでテキストファイルからTitleとbodyを読み込みます。viewHandler, editHandler, saveHandlerでrenderTemplateを使ってページを生成します。それをHandleFuncで呼び出すという形です。

感想

ここからどうやってポートフォリオにつなげていいのか二の足を踏んでる状態です。何か作ったほうがいいとはわかってはいるのですが…

go言語 並列実行入門

Go言語の並列実行について学んだメモです。

func goroutine(s string, wg *sync.WaitGroup){
  defer wg.Done()
  for i := 0; i < 5; i++{
    fmt.Println(s)
  }
}
func normal (s string){
  for i := 0; i < 5; i++{
    fmt.Println(s)
  }
}
func main(){
  var wg sync.WaitGroup
  wg.Add(1)
  go goroutine("world", &wg)
  normal("hello")
  wg.Wait()
}

並列実行は、time.Sleepしないと並列実行している関数が実行される前にmain関数が終了しプログラムが終わることがある。それを防ぐためにsync.WaitGroupを使う wg.Add(1)をしたら同じ数wg.Done()を呼ばないとエラーになる

goroutineを使って並列実行するが、これらは別のプログラムなのでreturnで値を返せない。そのため、channelを使って同期させる。

func goruoutine1(s []int, c chan int){
  sum := 0
  for _, v := range s{
      sum += v
  }
  c <- sum
}

func main(){
  s := []int{1, 2, 3, 4, 5}
  c := make(chan int)
  go goroutine1(s, c)
  x := <-c
  fmt.Println(x)
}

mainからスライスのsとchanのcを送って、goroutineが走る。x:=<-cの部分でgoroutineのc<-sumが終わるまで止まる。 chanを複数作ってもいいし、1つのchanを複数の関数で使ってもよい。順番はキューの順番になる。

func main(){
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    ch <-300
    fmt.Println(len(ch))
}

はエラーになる。make(chan int, 2)の2はバッファで、chの最大個数を示す。 よって、2で定義したのに3つ目を入れようとしたのでエラーになる。 取り出しているとエラーにならない。取り出すのは

s := <- ch

という記法。

 func main()){
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    close(ch)

    for c := range ch{
        fmt.Println(c)
    }
 }

chanを取り出すときにfor range文を使いたいが、close(ch)を書かずに使うとエラーになる。バッファの個数以上取り出そうとするため。close(ch)を書いていればエラーにならない

 func goruoutine1(s []int, c chan int){
  sum := 0
  for _, v := range s{
      sum += v
  }
  c <- sum
}

func main(){
  s := []int{1, 2, 3, 4, 5}
  c := make(chan int)
  go goroutine1(s, c)
  x := <-c
  fmt.Println(x)
}

これの、計算の途中過程を逐次表示するようにしたいなら、

func goruoutine1(s []int, c chan int){
  sum := 0
  for _, v := range s{
      sum += v
      c <- sum
  }
  close(c)
}
func main(){
  s := []int{1, 2, 3, 4, 5}
  c := make(chan int, len(s))
  go goroutine1(s, c)
  for i := range c{
      fmt.Println(i)
  }
}

close(c)はエラー防止。chanの数をメモリに確保したいならmake(chan int, len(s))とする。

func producer(ch chan int, i int){
    //Something
    ch <- i * 2
}

func consumer(ch chan int, wg *sync.WaitGroup){
    for i := range ch{
        func (){
            defer wg.Done()
            fmt.Println("process", i * 1000)
        }()
    }  
}
func main(){
    var wg sync.WaitGroup
    ch := make(chan int)

    //producer
    for i := 0; i < 10; i++{
        wg.Add(1)
        go producer(ch, i)
    }

    //consumer
    go consumer(ch, &wg)
    wg.Wait     //すべてのWaitGroupが終わるまで待つ
    close(ch)
}

close(ch)がこの位置にあるのは、consumerのforループがまだchanがあると思って待っているため。 それを終わらせるためにclose(ch)をする。

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    //time.tickとtime.Afterはchを返す関数。
    for {
        select {
        case <-tick: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る
            fmt.Println("tick.")
        case <-boom: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る
            fmt.Println("BOOM")
            return
        default:
            fmt.Println("   .")
            time.Sleep(50 * time.Millisecond)
        }
    }
    fmt.Println("#########")
}

for select文は、複数のチャネルを受信してチャネルによって処理を変えられる。selectは、for文の中にあるからずっと回る。for文がないと止まる。終わりたいなら、for文をbreakする 最後のfmt.Printlnはこのままだと実行されない。先にmain関数が終わってしまうため。実行したいなら、

OuterLoop2
 for{
    select{
    case <- tick://返ってくるchを変数に入れなくてもよい。入れると時刻も出る
        fmt.Println("tick.")
    case <-boom://返ってくるchを変数に入れなくてもよい。入れると時刻も出る
        fmt.Println("BOOM")
        break OuterLoop2
    default:
        fmt.Println("   .")
        time.Sleep(50*time.Millisecond)
    }
 }
    fmt.Println("#########")

のようにして、ラベルを使ってfor文の上に出る。名前は何でもいい。

package main

import (
    "fmt"
    "sync"
    "time"
)

 func main() {
    c := make(map[string]int)
    go func() {
        for i := 0; i < 10; i++ {
            c["key"] += 1
        }
    }()
    go func() {
        for i := 0; i < 10; i++ {
            c["key"] += 1
        }
    }()
    time.Sleep(1 * time.Second)
    fmt.Println(c, c["key"])
 }

このコードだと、成功したりエラーを出したりする。両方のゴルーチンがcへの書き込みをしており、cへの書き込みが同時になるとエラーになる。よって、sync, mutexを使う。

package main

import (
    "fmt"
    "sync"
    "time"
)
type Counter struct {
    v   map[string]int
    mux sync.Mutex
}

func (c *Counter) Inc(key string) {
    c.mux.Lock()
    defer c.mux.Unlock()
    c.v[key]++
}

func (c *Counter) Value(key string) int {
    c.mux.Lock()
    defer c.mux.Unlock()
    return c.v[key]
}
func main() {
    c := Counter{v: make(map[string]int)}
    go func() {
        for i := 0; i < 10; i++ {
            //c["key"] += 1
            c.Inc("Key")
        }
    }()
    go func() {
        for i := 0; i < 10; i++ {
            //c["key"] += 1
            c.Inc("Key")
        }
    }()
    time.Sleep(1 * time.Second)
    fmt.Println(c, c.Value("Key"))
}