20210121のC#に関する記事は5件です。

Hello Xamarin! MVVM編

この記事は前回のHello Xamarin!の続きです。

目的

前回の記事で書いた四則演算をするアプリをMVVMで作る。

MVVMとは


MVVMとは、Model - View - ViewModel の頭文字を取った設計パターンです。
Modelはビジネスロジックを、Viewは表示、入力を、ViewModelはViewのロジックを担当します。
こちらの記事が詳しいです。
MVVMパターンの常識 ― 「M」「V」「VM」の役割とは?

ViewModelとModelを適切に設計できれば、View(プラットフォーム)に依存しない再利用性が高いコードができあがります。

できたもの

スクリーンショット 2021-01-21 0.41.35.png

スクリーンショット 2021-01-21 0.39.09.png

前回作ったものに、0で割ろうとすると計算できないようにする機能をつけました。

コード

View(Xaml)

MainPage.Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MvvmCalculater.MainPage"
             xmlns:local="clr-namespace:MvvmCalculater">

    <ContentPage.BindingContext>
        <local:MainPageViewModel/>
    </ContentPage.BindingContext>

    <StackLayout Orientation="Vertical" Margin="30,50,0,0">
        <StackLayout Orientation="Horizontal">
            <Entry Text="{Binding LeftNumber}" WidthRequest="100" VerticalOptions="Start"/>
            <Picker ItemsSource="{Binding Operators}" SelectedIndex="{Binding SelectedIndex}"/>
            <Entry Text="{Binding RightNumber}" WidthRequest="100" VerticalOptions="Start" >
            </Entry>
            <Label Text="=" VerticalOptions="Center"/>
            <Label Text="{Binding Answer}" WidthRequest="100" VerticalOptions="Center"/>
        </StackLayout>
        <Button Text="計算" Command="{Binding CalculateCommand}"/>
    </StackLayout> 


</ContentPage>

ContentPageにMainPageViewModelをバインドします。

ViewModel(C#)

MainPageViewModel.cs
 public class MainPageViewModel : INotifyPropertyChanged
    {
        enum Operator
        {
            Add = 0,
            Sub = 1,
            Mul = 2,
            Div = 3
        }

        static readonly string[] operators = new[] { "+", "-", "×", "÷" };

        public DelegateCommand CalculateCommand { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        readonly Calculater calculater = new Calculater();

        public double LeftNumber { get; set; }


        double rightNumber;
        public double RightNumber
        {
            get => rightNumber;
            set
            {
                rightNumber = value;
                UpdateIsValid();
            }
        }

        double answer;      
        public double Answer
        {
            get => answer;
            set
            {
                answer = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Answer)));
            }
        }

        public string[] Operators { get; private set; }

        int selectedIndex;
        public int SelectedIndex
        {
            get => selectedIndex;
            set
            {
                selectedIndex = value;
                UpdateIsValid();
            }
        }

        public bool IsValid { get; private set; } = true;

        public MainPageViewModel()
        {
            Operators = operators;
            CalculateCommand = new DelegateCommand(Calculate, () => IsValid);   
        }

        void Calculate() => Answer = calculater.Calculate(LeftNumber, RightNumber, Operators[SelectedIndex]);

        void UpdateIsValid()
        {
            IsValid = !(selectedIndex == (int)Operator.Div && rightNumber == 0.0);
            CalculateCommand.RaiseCanExecuteChanged();
        }
    }

ViewModelのプロパティが変更したことをViewに通知するため、INotifyPropertyChangedを実装し、イベントを発火させます。
今回はプロパティの数が少ないのでそんなに苦ではないですが、数が増えるとsetterで毎回同じようなことを書かないといけないのでだるいです。
そこで普通はINotifyPropertyChangedを実装したベースクラスを作ってそれを継承します。

BindableBase
public abstract class BindableBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T oldValue, T value, [CallerMemberName] string propertyName = null)
        {
            if (oldValue.Equals(value)) {
                return false;
            } else {
                oldValue = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return true;
            }
        }

        protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
BindableBaseを継承したViewModel
public class ViewModel : BindableBase
{
        double answer;
        public double Answer
        {
            get => answer;
            set => SetProperty(ref answer, value);
        }
    }

setterでSetProperty()を呼ぶことで変更も通知もしてくれます。

Command

ボタンを押した時の処理などはICommandインターフェースを実装したCommandクラスにやらせます。actionに処理、canExecuteに実行できるかどうか、デリゲートで渡してあげます。

DelegateCommand
public class DelegateCommand : ICommand
{
        Action action;
        Func<bool> canExecute;
        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action action, Func<bool> canExecute)
        {
            this.action = action;
            this.canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return canExecute();
        }

        public void Execute(object parameter)
        {
            action?.Invoke();
        }

        public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);  
    }

これらのBindableBaseやDelegateCommandといったクラスはPrismというライブラリにあるので、それを素直に使いましょう。

Model(C#)

Calculater.cs
public class Calculater
{
        public double Calculate(double leftNumber, double rightNumber, string ope)
        {
            switch (ope) {
                case "+":
                    return leftNumber + rightNumber;
                case "-":
                    return leftNumber - rightNumber;
                case "×":
                    return leftNumber * rightNumber;
                case "÷":
                    if (rightNumber == 0) {
                        throw new InvalidOperationException();
                    } else {
                        return leftNumber / rightNumber;
                    }

                default:
                    throw new InvalidOperationException();
            }
        }
    }

まとめ

四則演算アプリをMVVMパターンで作成しました。
前回のコードビハインドでやった場合と比べてコード量がかなり増えました。
小規模のアプリなら素直にコードビハインドで書いた方が楽ですね。
本当は入力チェックをやろうと思ったんですが、WPFと違いINotifyDataErrorInfoを実装するだけじゃできないぽい。(調査不足で違うこと言ってたらすみません)
ダルかったので今回は省きました。

人生で2回目のQiitaですが、記事を書くのってかなり大変なんですね。
知識を共有してくれる世の中の素晴らしい先輩エンジニアの方々には頭があがりません。
いつもありがとうございます。

次はWPFで作ったアプリをXamarinに移行してみたいと思います。(できるかは知らん)

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

C#でAtCoder Beginners Selection(ABC087B - Coins)

準備

C#でAtCoderデビューのための準備
のあとで AtCoder Beginners Selection をやってみました。

問題文

ABC087B - Coins
https://atcoder.jp/contests/abs/tasks/abc087_b

提出結果

using System;

class Program
{
    static void Main(string[] args)
    {
        int a = int.Parse(Console.ReadLine());
        int b = int.Parse(Console.ReadLine());
        int c = int.Parse(Console.ReadLine());
        int x = int.Parse(Console.ReadLine());

        int choices = 0;
        for (int i = 0; i < a + 1; i++)
        {
            for (int j = 0; j < b + 1; j++)
            {
                for (int k = 0; k < c + 1; k++)
                {
                    if (((i * 500) + (j * 100) + (k * 50)) == x)
                    {
                        choices++;
                    }
                }

            }

        }
        Console.WriteLine($"{choices}");
    }
}

テスト実行

image.png
image.png
image.png

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

複数の型要素を持った項目をソートする方法 (C#)

背景

様々な要素が塊となった際にintの要素の項目でsort処理(昇順、降順)を行い、それに伴ってセットとなっている他の値も並び替える方法を探していた為。
アウトプットとしてまとめて使いやすくする為。

環境

  • Mac (macOS Big Sur 11.1)
  • Visual studio for mac (8.8.5)

使用言語

C#

メリット

ソート内容に沿って様々な型要素(例えばstring型やbool型、char型)を使用できる

書き方(言語化)

1.項目をクラス化する
2.クラスにした内容をインスタンス化する
3.処理する際に使用するListの変数を用意する
4.クラス化した変数に入力したい情報を塊として入れていく
5.Listに要素を追加していく
6.int型の要素の差を比較する
7.その差を利用して要素を並び替える
8.(もし降順にしたい場合は逆にする※いきなり降順はこの場合は不可)
9.要素をforeachで出力する

書き方(code)

using System;
using System.Collections.Generic;

namespace test_for_Paiza
{
    //ここのクラスに情報をまとめる
    class Info
    {
        public string name;
        public int age;

        //情報は直接変数にアクセスできない様にメソッド化する
        public Info(string name, int age)
        {
            this.name = name;
            this.age = age;
        }
    }

    class Program
    {
        static void Main()
        {
            //今回はコンソールから入力したものとする

            //入力回数を指定
            var N = int.Parse(Console.ReadLine());

            //Infoクラスをインスタンス化
            Info[] src = new Info[] { };

            //操作しやすい様に格納するListを生成
            var list = new List<Info>();

            //コンソールからの入力をForループで可変長にする
            for (int i = 0; i < N; i++)
            {
                string[] baseArray = Console.ReadLine().Trim().Split(' ');
                int intData = int.Parse(baseArray[1]);
                //Info型の変数srcに新規内容として格納
                src = new Info[] { new Info(baseArray[0],intData) };

                // listに要素を都度追加
                list.AddRange(src);
            }

            // listをソート
            var c = new Comparison<Info>(Compare);
            list.Sort(c);

            //降順にしたい場合はReverseメソッドを使用
            //list.Reverse();

            Console.WriteLine("以下からがソート済の内容です");

            foreach (Info i in list)
            {
                Console.WriteLine("name:{0}, age:{1}才", i.name, i.age);
            }


        }

        static int Compare(Info a, Info b)
        {
            //xには2つの値の差が入る
            var x = a.age - b.age;
            return x;
        }
    }
}

<入出力結果>
5
hideki 40
koichi 25
yohko 18
ken 34
miho 29
以下からがソート済の内容です
name:yohko, age:18才
name:koichi, age:25才
name:miho, age:29才
name:ken, age:34才
name:hideki, age:40才

まとめ

書き方については他の方の書き方を参考にさせていただきながら書いてみました。ありがとうございます。
クラス化させることで追加で要素を増やしたくなった場合にも柔軟に対応できるのかなと思いました

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

Azure Cloud Services (Classic) を REST API から操作する

はじめに

この記事は、昔々 Azure が Windows Azure と呼ばれていた頃に使われていた Azure Service Management (ASM) という、太古のテクノロジーに関する話題です。本記事の著者も久しぶりに触る機会があり、昔のことを忘れていて結構悩みました。なので、いま知識を残しておかないと、今後ほかにも突然保守を任された方などが苦労されるかも…と思い、メモ書き程度に残しています。

Azure Resource Manager モデルで同じようなことをやろうとする際には、ここに書いてある内容では全く上手くいきませんので、こちら の記事を参考にして頑張ってください。

Azure Service Management (ASM) とは

Azure が出来た当初に利用されていた Azure のサービス管理とデプロイメントの管理の仕組みです。現行の Azure Resource Manager (ARM) と対比して、クラシックデプロイモデル、なんて言われることもあります。
2014年に Azure Resource Manager が出た後は、着々と Azure 上のサービスの ARM 対応が進められてきており、今では ASM の利用は非推奨となっていますので、今を生きる人はこのあたりで回れ右で OK です。

但し、Azure Cloud Services など、ごく一部のサービスで ASM での管理にしか対応していないものがあり、このあたりの操作には必然的に ASM を利用する必要があります。

ASM でデプロイされたサービスを REST API から操作するには?

ARM のリソースをプログラムから弄るときには、サービスプリンシパルを作ってやるのが一般的ですが、ASM にはそういったオシャレなものは用意されていません。

ではどうするのか?というと「管理証明書」をサブスクリプションに登録し、その「管理証明書」を REST API 呼び出しのクライアント証明書につけることで、ユーザー認証を行っています。

手順

サブスクリプションに管理証明書を登録するには

まずは、自己署名証明書で良いので、何らかの証明書を作る必要があります。

PowerShell で作るならこんな感じ。

MakeOreOre.ps1
$cert = New-SelfSignedCertificate -Subject "適当に名前をつける" -CertStoreLocation "cert:\CurrentUser\My" -KeyLength 2048 -KeySpec "KeyExchange"
$password = ConvertTo-SecureString -String "パスワード" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\my-cert-file.pfx" -Password $password
Export-Certificate -Type CERT -Cert $cert -FilePath .\my-cert-file.cer

これで作った my-cert-file.cer を以下の手順で管理対象の Cloud Services が存在するサブスクリプションに登録します。

Azure サービス管理証明書のアップロード

Cloud Services を REST API で操作するには

まずは以下のリファレンスを読みましょう。

Cloud Services REST Api

例えば、あるデプロイメントの情報を取得するとしたら、以下のドキュメントに従ってリクエストを ASM REST API に投げればいいことになります。

Get Deployment

Get Deployment REST API の呼び出しを C# のコードからやるとしたら

HttpWebRequest を使ってリクエストを投げてやるのが簡単だと思います。
こんな感じ。
応答は時代を感じさせる XML で返ってきますのでご注意ください。

ASMSample.cs
string subscriptionId = "サブスクリプション ID";
string cloudserviceName = "クラウドサービス名";
string slot = "production"; //production か staging

//証明書を読み込む
cert = new X509Certificate2("pfxファイルのパス", "PFXファイルのパスワード");

//リクエストの組み立て
Uri uri = new Uri(string.Format(
    "https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}",
    subscriptionId, cloudserviceName, slot));

HttpWebRequest request =
    (HttpWebRequest)HttpWebRequest.Create(uri);
request.Method = "GET";
request.ClientCertificates.Add(cert);
request.Headers.Add("x-ms-version", "2009-10-01");

//リクエスト出す
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

//Status == OK なら呼び出し成功している
if (response.StatusCode == HttpStatusCode.OK)
{
    XElement body = XElement.Load(response.GetResponseStream());
    XNamespace nspace = body.Name.Namespace;
    //ここまできたら、body のなかに応答の XML が入っているので煮るなり焼くなりご自由に。
    //クエリをかけるときには、名前空間付きの XML であることに要注意です。
}

ここまでわかってしまえば、ほかの API もこれの応用でどうにかなります。

まとめ

太古の Azure のテクノロジーをこの 2021 年にやらなければいけなくなってしまった人の手助けになりますように…。

補足

Azure Service Management は、現在着々と機能縮小が進んでいます。
例えば、ASM を使ってデプロイされたクラシックデプロイモデルに基づく仮想マシンは、2023/3/1 までに ARM へ移行することが求められています。
2023 年 3 月 1 日までに IaaS リソースを Azure Resource Manager に移行する
是非、これから Azure でサービスをご利用される方は、この記事を参考にしないようにご注意ください。
(基本的には、ポータルからはちょっと変な操作をしないと出てこないところに引っ込んでいるので、普通にポータルから仮想マシンを作る、とかはやっちゃって全然 OK です。そしたら ARM モデルになってますので。)

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

「システム コールに渡されるデータ領域が小さすぎます」について

はじめに

OpenCLを用いた演習をしていて,タイトルにある通り, エラーに悩まされていましたが,不意に解決しました.
使用教材はC#によるOpenCL並列プログラミングという本です.

環境

Visual Studio 2019

概要

入力された画像に対して, カーネルプログラムで書いたラプラシアンフィルタを適用するプログラムです.
以下のようなエラー文が出力されていました.

console
System.Runtime.InteropServices.COMException (0x8007007A): システム コールに渡されるデータ領域が小さすぎます。 (HRESULT からの例外: 0x8007007A)

本の通りにタイピングしたつもりだったのですが...

色々調べてみてもBufferの確保の話だとか,UTF-8関連の話だとか様々でした.

他にもコンソールアプリケーションとあまり関係なさそうなものまでヒットしてわけわかめでした.

コード

カーネルプログラムは以下のようになっていました.

lap8.cl
__kernel void
clCode(__global const unsigned char in[],
        __global unsigned char out[],
        __const int width)
{
    int yx[3][3];        // 二次元配列

    size_t x=get_global_id(0);
    size_t y=get_global_id(1);

    int stride=4*width;

    for(int dY=0; dY<3; dY++)
        for(int dX=0; dX<3; dX++)
            yx[dY][dX]=(y+dY)*stride+(x+dX)*4;

    for(int rgb=0; rgb<3;rgb++)
    {
        int data=(int)(
            -in[yx[0][0]+rgb] -in[yx[0][1]+rgb] -in[yx[0][2]+rgb]
            -in[yx[1][0]+rgb] +in[yx[1][1]+rgb]*8 -in[yx[1][2]+rgb]
            -in[yx[2][0]+rgb] -in[yx[2][1]+rgb] -in[yx[2][2]+rgb]
        );

        out[yx[1][1]+rgb]=convert_uchar_sat(data);
    }
}

他のカーネルプログラムは問題なく実行できていたため,おそらくカーネルが悪いのだろうという結論になり5時間くらい長考していました...()

結論

5時間調べ物をしていてふとした瞬間に
「UTF-8? bufferのメモリ? コメントアウトいる?」
みたいな思考になり,// 二次元配列を消してみました.

するとあら不思議.頑なに動かなかったプログラムが動くではないですか.

ちなみに英語でコメントした場合問題なく動いたので,どうやら'UTF-8'の日本語入力でコメントを打つとうまくいかないみたいです.

カーネルプログラムは以下のようになりました.

lap8.cl
__kernel void
clCode(__global const unsigned char in[],
        __global unsigned char out[],
        __const int width)
{
    int yx[3][3];

    size_t x=get_global_id(0);
    size_t y=get_global_id(1);

    int stride=4*width;

    for(int dY=0; dY<3; dY++)
        for(int dX=0; dX<3; dX++)
            yx[dY][dX]=(y+dY)*stride+(x+dX)*4;

    for(int rgb=0; rgb<3;rgb++)
    {
        int data=(int)(
            -in[yx[0][0]+rgb] -in[yx[0][1]+rgb] -in[yx[0][2]+rgb]
            -in[yx[1][0]+rgb] +in[yx[1][1]+rgb]*8 -in[yx[1][2]+rgb]
            -in[yx[2][0]+rgb] -in[yx[2][1]+rgb] -in[yx[2][2]+rgb]
        );

        out[yx[1][1]+rgb]=convert_uchar_sat(data);
    }
}

コメントアウトが消えただけですね.

日本語の影響で使用するメモリサイズに対して確保したメモリの大きさが足りなくなったような感じなのですかね?

検証は重ねていませんが,カーネルプログラムでは日本語入力に気をつけようって話でした.

私が忘れないうちにここにメモしておきます.:file:

それでは:wave:

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