20190607のC#に関する記事は10件です。

対象のリストからフィルタに含まれない要素を除外する (C#)

前提

  • Linqは使いたくないものとします。
  • staticクラス内で定義しない(拡張メソッドにしない)場合は、第1引数のthisを取ってください。

やりたいこと

  • 対象が{ B, C, D, E, F }で、フィルタが{ A, B, D, E, G, H }のとき、{ B, D, E }を得たいです。

コード

対象のリストを書き替える場合

TargetList.RemoveAll (t => !FilterList.Contains (t));

新しいリストを返す場合

var result = TargetList.FindAll (t => FilterList.Contains (t));

ダメなやり方

この方法だと、リストを前から順にスキャンするループ中に要素が削除されてズレるので、スキャンから漏れる要素が生じます。

for (var i = 0; i < TargetList.Count; i++) {
    if (!FilterList.Contains (target [i])) { TargetList.RemoveAt (i); }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#メモ 非同期main

~C# 7.0

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    private static async Task MainAsync()
    {
        await Task.Delay(2000);
        Console.WriteLine("Hello World!");
    }
}

C# 7.1~

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        await Task.Delay(2000);
        Console.WriteLine("Hello World!");
    }

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

.NET Core プロジェクトによる元号(和暦)表示の注意点

この記事は

 数ヶ月前に散々騒がれていた新元号とはまた別の話です。

 具体的には、元号を アルファベット一文字 で表記する際の注意点になります。

前提条件

 環境は下記の通りになります。

  • Windows 10 1809 Build 17763.475
  • CentOS 7.6.1810
  • どちらも .NET Core 3.0.100-preview5-011568

元号表示に利用するコード

 基本的な部分は一般的な元号表記と同様に System.Globalization 名前空間の CultureInfoJapaneseCalendar を使用しています。

var cultureInfo = new CultureInfo("ja-JP", false);
var dateTimeFormat = cultureInfo.DateTimeFormat;

dateTimeFormat.Calendar = new JapaneseCalendar();

var eraSymbols = new Dictionary<int, char>();

for (var eraSymbol = 'A'; eraSymbol <= 'Z'; eraSymbol++)
{
    var eraIndex = dateTimeFormat.GetEra(eraSymbol.ToString());

    if (eraIndex > 0)
    {
        eraSymbols.Add(eraIndex, eraSymbol);
    }
}

if (eraSymbols.Any())
{
    foreach (var pair in eraSymbols)
    {
        Console.WriteLine($"eraIndex = {pair.Key}, eraSymbol = {pair.Value}");
    }
}
else
{
    Console.WriteLine($"{nameof(eraSymbols)} is empty.");
}

 このコードをWindows上で実行すると、下記のような出力結果になります。

eraIndex = 4, eraSymbol = H
eraIndex = 1, eraSymbol = M
eraIndex = 5, eraSymbol = R
eraIndex = 3, eraSymbol = S
eraIndex = 2, eraSymbol = T

 念のためにもっとわかりやすく表現すると、こういうことです。

eraIndex eraSymbol 元号
1 M 明治
2 T 大正
3 S 昭和
4 H 平成
5 R 令和

この eraIndex は、例えば上記のコードの続きに dateTimeFormat.GetEraName(eraIndex) というようにすれば漢字表記で元号を返します。また、 dateTimeFormat.GetAbbreviatedEraName(eraIndex) とすれば漢字表記の先頭一文字目を返します。

var eraIndex = 1;
dateTimeFormat.GetEraName(eraIndex); // => 明治
dateTimeFormat.GetAbbreviatedEraName(eraIndex) // => 明

 これにより、 eraIndex をもとに漢字二文字の正式名称、漢字一文字の略称、アルファベット一文字の略称を利用できるわけなのです。

 ここまでがこの記事の導入になります。

問題点

 若干いまさらなお話ですが、.NET Coreというのはクロスプラットフォームでの動作になるので、同様の処理をLinuxでも行えるはずなのです。当然、Windows環境で作成した上記のコードもLinuxで実行することができます。

 ということで、CentOS上で上記コードを実行しました。

$ dotnet run
eraSymbols is empty.

 上記コードでDictionaryが空っぽだった際のコンソール出力処理があることもあり予定調和的ですが、CentOS上では eraIndex を取得することができませんでした。

 では一体、それはなぜなのでしょうか?

そもそも元号情報はどこから来るのか

 簡潔に言うと元号情報は、Windowsで実行すればレジストリから、macOSやLinuxの環境ではlibicuから取得します。そのため、改元の対応としてWindows Updateによるレジストリの更新やlibicuの更新のみで元号を追加することができます。

 ちなみに、レジストリからもlibicuからも取得できなかった場合は、.NET Coreのソースにハードコーディングされている情報を引っ張ります。

 このあたりは下記ページが参考になります。

 そういった情報を踏まえると、WindowsとLinuxで動作が異なるのは「レジストリとlibicuで元号情報が異なっているのではないか?」という考えにいたります。

実際の処理内容

 調査するにあたって、実際に System.Globalization.DateTimeFormatInfo.GetEraNameソースコードを見てみましょう。

 簡単にすると、以下の手順で処理しています。

  1. EraNames に引数がないか探す。
  2. AbbreviatedEraNames に引数がないか探す。
  3. AbbreviatedEnglishEraNames に引数がないか探す。

 これらはすべてstring型の配列でそれぞれ指数が同じ元号を示すようになっているため、引数が見つかった段階でそのインデックス + 1の値を返しています。なお、ソースコードを見ればわかりますが、正しくは上記3つの配列(プロパティ)ではなく異なる配列を探しています。ですがこの場では命名としてこちらのほうが意味がわかりやすいことと、そもそも中身が同じなので、このようにしています。

 さて、処理内容を見てみると、どうやらここで探している配列にレジストリやlibicuから取得した元号情報があるのではないかと予測することができますね。

 では実際にレジストリに登録されている元号情報を見てみると、下記のようになっています。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras
明治_明_Meiji_M
大正_大_Taisho_T
昭和_昭_Showa_S
平成_平_Heisei_H
令和_令_Reiwa_R

 これを見たときに気になることがありませんか?

 System.Globalization.DateTimeFormatInfo.GetEraName で探している対象の配列は3つなのに、レジストリを見ると4つのパターンがあるということについて、気になりますよね?

 処理の中で3パターンの表記の中から、引数の表記パターンが見つかったときにそのインデックスが返ってきます。つまり、4パターンの表記があるレジストリの持つ値のうち、引数で渡すとインデックスが返ってこない仲間はずれがいるのでは……?

 下記のコードをWindowsとCentOSでそれぞれ実行して確認してみましょう。

Console.WriteLine($"平成 = {dateTimeFormat.GetEra("平成")}");
Console.WriteLine($"平 = {dateTimeFormat.GetEra("平")}");
Console.WriteLine($"Heisei = {dateTimeFormat.GetEra("Heisei")}");
Console.WriteLine($"H = {dateTimeFormat.GetEra("H")}");
Windows
平成 = 4
平 = 4
Heisei = -1
H = 4
CentOS
平成 = 4
平 = 4
Heisei = 4
H = -1

 ……やっぱり仲間はずれがいましたし、さらに環境によっては仲間はずれにされるのが異なっているようですね。

 探す対象になっている3つの配列はそれぞれ直訳すると 「元号」「省略された元号」「省略された英語の元号」 となりますし、レジストリから取得した際の出力結果のほうが意味として正しいように感じます。

まとめ

  • .NET Core はクロスプラットフォームだけど、なんでも同じように動くわけではない。(少なくとも現在は)
  • この記事の内容に関しては改元の話で盛り上がるよりも前から遭遇していたし解決もしたが、いつかまとめようと思っていたら改元の話題が始まって終わっていた。

 Qiita初投稿なのでなにか書き方が間違っていたりした場合は教えてください。もちろん、技術的に間違っている場合もどんどんご意見をください。

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

[WPF/xaml]Storyboardでアニメーションをつくる2(TargetPropertyの階層的な指定)

やりたいこと

以前、Storyboardでアニメーションをつくる方法を勉強したが、その中の「DoubleAnimationUsingKeyFrames」でTargetPropertyを指定している部分の、

a.xaml
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)">

の「(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)」の部分が、どういうことを書いているのか実はよくわかってなかった。
この際はっきりさせたい。

以前のコードはこちら参照
https://qiita.com/tera1707/items/a7fcdd95fc3120ae3c8b

調べた時に見たサイト

こちらに、知りたいことはすべて書いてあった。

アニメーション(WPF)(++C++)
https://ufcpp.net/study/dotnet/wpf_xamlani.html
>「TergetProperty を階層的に指定」
>「TergetProperty で配列的にアクセス」

書いてあったが、見るだけでは勉強にならないので、自分なりの理解を書いてみようと思う。

自分なりの理解

別の書き方

Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"

Storyboard.TargetProperty="RenderTransform.Children[0].X"
は、同じことを表している。

②を省略せずに正しく書くと、①の書き方になる。

②の意味合い

②は、日本語でいうと、下記のようなことを表している。

  • 対象(ここではRectangle(名前はslider))に含まれている、
  • RenderTransformにセットされているもの(Childrenの0番目)の、
  • Xをターゲットプロパティにする。

②で省略していること

②の書き方では。いろいろと省略されていることがある。

  • RenderTransformは正しく言うと、UIElementクラスで定義されているメンバRenderTransformである。
  • Childrenは正しく言うと、TransformGroupクラスで定義されているメンバChildrenである。
  • Xは正しく言うと、TranslateTransformクラスで定義されているメンバXである。

①の意味合い

で、書き方としては、省略しているものを省略無しで正しく書こうとするとカッコ()を付けるようなので、

  • RenderTransform → (UIElement.RenderTransform)
  • Children     → (TransformGroup.Children)
  • X         → (TranslateTransform.X)

となり、それぞれをつなげると、つまり①の内容になる。

前提

①または②のように書くと、Storyboardは動いてくれるのだが、前提として対象のRectangle(名前はslider)のRenderTransformに、
image.png
「TranslateTransform」をセットした「TransformGroup」がセットされてないと、例外で動かなかった。これは、"RenderTransform.Children[0].X"の中の右半分、「.Children[0].X」が存在しなかったからと思われる。

自分で意図してセットしないと、RectangleのRenderTransformにはなにも入っていないので、Storyboardでターゲットプロパティとして指定するためには、「Storyboard動作時に」TranslateTransformが入ったTransformGroupがセットされていることが前提となる。
(セットしてなくても、アプリ起動時には落ちない。Storyboard動作時に落ちる。)

(UIElement.RenderTransform)の「UIElement」はどこから来た?

今回のstoryboardのターゲットはRectangleで、そのRectangleは「UIElement」を継承してできている。

Rectangle Class
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.rectangle?view=netframework-4.8
image.png

UIElementクラスで「RenderTransform」は定義されいているので、「(UIElement.RenderTransform)」と書く。

※ためしに、(UIElement.RenderTransform)のところを(FrameworkElement.RenderTransform)とか(Shape.RenderTransform)とか書いても動くのは動く。

自分なりの結論

a.xaml
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)">

上記のStoryboard.TargetPropertyの書き方は、省略をせずに、階層的にプロパティを書いたものだった。

別の例

別の例として、背景の色を時間を追って変化させたいときに、下記のようにColorAnimationUsingKeyFrames を書いたとする。

a.xaml
<ColorAnimationUsingKeyFrames  BeginTime="00:00:00"  Storyboard.TargetName="BackgroundBorder" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)">

この中のStoryboard.TargetPropertyは、

  • 対象に含まれている、
  • Backgroundにセットされているもの(SolidColorBrush)の、
  • Colorをターゲットプロパティにする。

ということ。省略して書くと、

a.xaml
<ColorAnimationUsingKeyFrames  BeginTime="00:00:00"  Storyboard.TargetName="BackgroundBorder" Storyboard.TargetProperty="Background.Color">

となる。またこの場合も、対象(ここではBackgroundBorderという名前のBorder)のBackgroundがあらかじめ設定されていないと例外で落ちる。

この書き方をAttributeSyntaxというらしい.xaml
<!-- ここで、Background="Gray"がないとStoryboard実行時に落ちる -->
<Border Name="BackgroundBorder" CornerRadius="2" Height="28" Width="60" BorderBrush="White" BorderThickness="2" Background="Gray"/>

Background="Gray"は、Border のBackgroundに、グレー色のSolidColorBrushがセットされている、ということを示す。
(xamlが勝手にそういうことをしてくれている)

それがないと落ちてしまうのは、Storyboardが変化させようとするColorを持っているSolidColorBrushが存在しないからだと思われる。

Background="Gray"は、Border のBackgroundに、グレー色のSolidColorBrushがセットされている」というのは、個人的には下記のように書くとイメージが湧きやすかった。

この書き方を「PropertyElementSyntax」というらしい.xaml
<Border Name="BackgroundBorder" CornerRadius="2" Height="28" Width="60" BorderBrush="White" BorderThickness="2">
    <Border.Background>
        <SolidColorBrush Color="Gray"/>   <!-- ←これがないと、storyboardが設定するものがなくて落ちてしまう。 -->
    </Border.Background>
</Border>

参照

アニメーション(WPF)(++C++)
https://ufcpp.net/study/dotnet/wpf_xamlani.html
>「TergetProperty を階層的に指定」
>「TergetProperty で配列的にアクセス」

XAML の基本構造(WPF)
Attribute SyntaxとかProperty Element Syntaxとか
https://ufcpp.net/study/dotnet/wpf_xamlbasic.html?key=attribute#attribute
>「プロパティの設定」

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

C#製APIから取得したJSONのKeyを大文字で揃えるには

.net core C#でwebAPIを作っているときに, GETしてきたjsonのkeyが大文字小文字入り乱れていて困った。

取得したjson

C#側でこんな感じの構造のデータをDBから取得するAPIを作成した

public class DISK_CONFIG_INFO
{
    [Key, Required]
    public string SERIAL_NO { get; set; }
    public string DISK_NAME { get; set; }
    public string MOUNT_POS { get; set; }
    public decimal? MAX_VOLUME { get; set; }
    public decimal? ROTATE { get; set; }
    public DateTime? INS_DATE { get; set; }
    public DateTime? UPD_DATE { get; set; }
}

これを実際にGETしてみるとこうなってしまった

{
    "Serial_no": "13J0DKSGS",
    "Disk_name": "TOSHIBA DT01ACA300",
    "Mount_pos": "/mnt/sdb",
    "Max_volume": 2793,
    "Rotate": 7200,
    "Ins_date": "2019-04-02T17:02:11",
    "Upd_date": "2019-04-03T15:30:03"
}

何故かjsonのkeyが全て大文字始まりの小文字になっている…

修正方法

結論として, startup.csの中でAddMvc()している箇所でjsonのシリアライズの設定をしてあげればよい

startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
  //省略
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  //省略
}

これを

startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
  //省略
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                   .AddJsonOptions(opt =>
                  {
                    opt.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
                  });
  //省略
}

こうしてあげる。

結果

再度同じGETメソッドを呼んで見る

{
    "SERIAL_NO": "13J0DKSGS",
    "DISK_NAME": "TOSHIBA DT01ACA300",
    "MOUNT_POS": "/mnt/sdb",
    "MAX_VOLUME": 2793,
    "ROTATE": 7200,
    "INS_DATE": "2019-04-02T17:02:11",
    "UPD_DATE": "2019-04-03T15:30:03"
}

これで大丈夫そうです。

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

読み取り専用に公開するパターン

外部からはReadOnlyにして安全な設計を実現する

TODO:めっちゃ面白い導入部を考える

あるオブジェクトに対して「読み込みと書き込みをする」機能と「読み込みだけをする」機能をそれぞれ切り出して公開する(=書き込みする必要のない人には読み込みする機能だけを公開する)ようにしておくと、オブジェクトの依存関係を(全部公開するのに比べて)減らすことができるようになります。

「読み込みと書き込みをする」機能というのは一種の比喩で、かっこいい言い方をすると「副作用のあるインターフェース」ということになります。

読み取り専用のプロパティ

単純にプロパティにアクセス権の差をつけるだけでも、何もしない(全部public)よりは効果があります。大抵のケースではこれだけで十分です。

public class PropertyExample
{
    public Something Item { get; private set; }
}

読み取り専用のコレクション

コレクションを公開するプロパティをgetterだけに限定しても、コレクションを参照して副作用のあるインスタンスメソッドを呼ばれてしまっては元も子もないです。

Listの場合

List<T>は読み取り専用のインターフェースだけをあつめたIReadOnlyList<T>を実装しているので、外から変更されたくないがコレクションは公開したい場合はIReadOnlyList<T>にキャストした状態で公開するといいです。

もちろん、呼び出し元でList<T>に強制的にキャストすればいじれてしまいます。プログラム的にこれを制限する方法はないように思うので、悪いプログラマがチームに紛れ込まないようにするしかないです(悪いプログラマがチームに紛れ込むと設計や言語仕様ではどうにもならないのはC#に限った話ではない…)。

public class ListExample
{
    public IReadOnlyList<Something> Items => this.ItemsSource;
    private List<Something> ItemsSource { get; set; } = new List<Something>();
}

ObservableCollectionの場合

List<T>はReadOnly版のinterfaceがありますが、ObservableCollection<T>(更新通知付きコレクション)にはありません。こちらの場合、interfaceではなく別途ReadOnlyObservableCollection<T>のインスタンスを作って元ネタのObservableCollectionを指定してやる、というのが必要になります。

public class ObservableCollectionExample
{
    public ReadOnlyObservableCollection<Something> Items { get; }
    private ObservableCollection<Something> ItemsSource { get; set; } = new ObservableCollection<Something>();

    public ObservableCollectionExample()
    {
        this.Items = new ReadOnlyObservableCollection<Something>(this.ItemsSource);
    }
}

公開範囲をもっと細かく設定する

設計的に一番ごちゃごちゃしないのは「クラスの外部に副作用のあるインターフェース(プロパティ、メソッド)を公開しない」なんですが、それだと設計コストがめちゃ高くなるので徹底するのは難しいです。っていうか現実的に可能なんですかね?

経験則として、あるオブジェクトの「状態を知りたい」人と、「状態を変更したい」人では、前者の方が数が多いので、オブジェクト自体は同じでも公開先に応じて公開するものを切り替えられるようになると小回りがきいて便利です。

そういうときには、先に示したList<T>IReadOnlyList<T>のように、同じクラスで読み取りの操作だけを公開するinterface書き込み操作も公開するinterfaceの2つを実装するようにして、公開先に応じてどちらのinterfaceを見せるか切り替えればよいです。

/// <summary>
/// 副作用のない操作だけを公開するReadOnlyなInterface
/// </summary>
public interface IReadOnlyMultistageExample
{
    /// <summary>
    /// 副作用のないプロパティ取得
    /// </summary>
    Something Item { get; }

    /// <summary>
    /// 副作用のないメソッド呼び出し
    /// </summary>
    Something GetSomething();
}

/// <summary>
/// 副作用のある操作を公開するInterface
/// </summary>
public interface IMultistageExample : IReadOnlyMultistageExample, IDisposable
{
    /// <summary>
    /// 副作用のあるプロパティ設定
    /// </summary>
    new Something Item { get; set; }

    /// <summary>
    /// 副作用のあるメソッド呼び出し
    /// </summary>
    Something DoSomething(Something arg);
}

/// <summary>
/// 実体クラス
/// </summary>
public class MultistageExample : IMultistageExample
{
    /// <summary>
    /// プロパティ
    /// </summary>
    public Something Item { get; set; }

    /// <summary>
    /// 副作用のあるメソッド呼び出し
    /// </summary>
    public Something DoSomething(Something arg)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// 副作用のないメソッド呼び出し
    /// </summary>
    public Something GetSomething()
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// オブジェクトの破棄
    /// </summary>
    public void Dispose()
    {
        throw new NotImplementedException();
    }
}

どっちのinterfaceで取得したかによって、呼び出せる操作の範囲が変わります。

MultistageExample implement = new MultistageExample();

{
    IMultistageExample ime = implement;
    ime.Item = new Something();
    var item = ime.Item;
    ime.DoSomething(new Something());
    item = ime.GetSomething();
    ime.Dispose();
}
{
    IReadOnlyMultistageExample irome = implement;
    // irome.Item = new Something(); コンパイルエラー
    var item = irome.Item;
    // irome.DoSomething(new Something()); コンパイルエラー
    item = irome.GetSomething();
    // irome.Dispose(); コンパイルエラー
}

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

BehaviorSubjectの初期値がない版

背景

BehaviorSubjectは、最後に発行された値をキャッシュしてくれるため、あとから購読(Subscribe)しても値が取得できるという特性を持つSubjectです。便利。

しかしながら、BehaviorSubjectはコンストラクタで初期値を設定してやる必要があるため、利用場面によっては流れてくる値が「初期値」なのか「発行された値(null値?)」なのかを区別できるように定義する必要があって面倒なときがあります(もちろん、初期状態を定義できるものであればBehaviorSubjectで十分なんですが)。

そこで、BehaviorSubjectの初期値がない版を作ってみました。

BehaviorSubjectの初期値がない版

期待する挙動としては、

事前にOnNext Subscribeしたときの挙動
されてる 事前にOnNextされた値が流れる
されてない 値が流れない

こんな感じです。

/// <summary>
/// 状態を持つSubject。Subscribeしたときに最後に通知された値を流す。一度も通知されていない場合は流れない。
/// </summary>
public class StatefulSubject<T> : ISubject<T>, IDisposable
{
    private readonly Subject<T> _Implement = new Subject<T>();

    private object _Value = default;

    private NotificationKind _LatestNotificationKind = NotificationKind.None;

    /// <summary>
    /// Provides the observer with new data.
    /// </summary>
    /// <param name="value">The current notification information.</param>
    public void OnNext(T value)
    {
        lock (this)
        {
            if (this._LatestNotificationKind == NotificationKind.Completed ||
                this._LatestNotificationKind == NotificationKind.Error)
            {
                return;
            }

            this._Value = value;
            this._LatestNotificationKind = NotificationKind.Next;
            this._Implement.OnNext(value);
        }
    }

    /// <summary>
    /// Notifies the observer that the provider has experienced an error condition.
    /// </summary>
    /// <param name="error">An object that provides additional information about the error.</param>
    public void OnError(Exception error)
    {
        lock (this)
        {
            if (this._LatestNotificationKind == NotificationKind.Completed ||
                this._LatestNotificationKind == NotificationKind.Error)
            {
                return;
            }

            this._Value = error;
            this._LatestNotificationKind = NotificationKind.Error;
            this._Implement.OnError(error);
        }
    }

    /// <summary>
    /// Notifies the observer that the provider has finished sending push-based notifications.
    /// </summary>
    public void OnCompleted()
    {
        lock (this)
        {
            if (this._LatestNotificationKind == NotificationKind.Completed || 
                this._LatestNotificationKind == NotificationKind.Error)
            {
                return;
            }

            this._Value = null;
            this._LatestNotificationKind = NotificationKind.Completed;
            this._Implement.OnCompleted();
        }
    }

    /// <summary>
    /// Notifies the provider that an observer is to receive notifications.
    /// </summary>
    /// <param name="observer">The object that is to receive notifications.</param>
    /// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
    public IDisposable Subscribe(IObserver<T> observer)
    {
        lock (this)
        {
            if (!this._Implement.IsDisposed && this._LatestNotificationKind != NotificationKind.None)
            {
                switch (this._LatestNotificationKind)
                {
                    case NotificationKind.Next:
                        observer.OnNext((T)this._Value);
                        break;
                    case NotificationKind.Error:
                    case NotificationKind.None:
                    case NotificationKind.Completed:
                        break;
                }
            }
            return this._Implement.Subscribe(observer);
        }
    }

    /// <summary>
    /// Indicates whether the subject has observers subscribed to it.
    /// </summary>
    public bool HasObservers => this._Implement.HasObservers;

    /// <summary>
    /// Indicates whether the subject has been disposed.
    /// </summary>
    public bool IsDisposed => this._Implement.IsDisposed;

    /// <summary>
    /// 破棄
    /// </summary>
    public void Dispose()
    {
        this._Implement?.Dispose();
    }

    /// <summary>
    /// 通知の種類
    /// </summary>
    private enum NotificationKind
    {
        /// <summary>
        /// 未通知
        /// </summary>
        None,

        /// <summary>
        /// OnNext
        /// </summary>
        Next,

        /// <summary>
        /// OnError
        /// </summary>
        Error,

        /// <summary>
        /// OnCompleted
        /// </summary>
        Completed,
    }
}

RxのSubjectのコードを見てるともっと賢い排他の方法を使ってるっぽくて、性能を追求する場合はそのあたりの改善が必要です。

いちおう、Rxの他のSubjectの動きを見ながらそれっぽく動くようにしたつもりですけど、挙動に変なところがあるかもしれません。お許しください。

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

SeleniumのChromeDriverでGoogle Chrome以外のブラウザを動かす

はじめに

ブラウザ自動テストのデファクトスタンダードであるSelenium
そのChromeDriverを使ってGoogle Chrome以外のブラウザを操作することができるのか?
という遊び。

動かせた時の感動は省略

やったこと

Chrome.exe

ここから取得:https://download-chromium.appspot.com/
そして適当なパスに配置する
※後述するが、取り直すことになった…

NuGet

Selenium系のモジュール参照
image.png

ソース

var options = new ChromeOptions
{
    // 結局、いつもと違うのはここだけ!
    BinaryLocation = @"C:\Win_x64_638880_chrome-win\chrome-win\chrome.exe"
};

// ヘッドレスでもできたよ!
options.AddArgument("--headless");

using (var driver = new ChromeDriver(options))
{
    var wait = new WebDriverWait(driver, new TimeSpan(0, 0, 5));
    driver.Navigate().GoToUrl("https://www.google.com");
    driver.FindElementByName("q").SendKeys("Chromium");
    driver.FindElementByName("q").Submit();

    wait.Until(ExpectedConditions.TitleIs("Chromium - Google 検索"));
    ((ITakesScreenshot)driver).GetScreenshot().SaveAsFile($"{DateTime.Now.ToString("yyyyMMddHHmmss")}.png");
}

はまったこと

ChromeのバージョンとChromeDriverのバージョンがずれているとエラーになるので注意
こちらを参照ください:Chromiumの特定バージョンをダウンロードする

エラーはこんな感じ

ChromiumDriverSample.ChromiumChromeDriverTest.Sample が例外をスローしました: OpenQA.Selenium.ElementNotInteractableException: element not interactable (Session info: chrome=75.0.3770.0) (Driver info: chromedriver=74.0.3729.6 (255758eccf3d244491b8a1317aa76e1ce10d57e9-refs/branch-heads/3729@{#29}),platform=Windows NT 10.0.17763 x86_64)

おわりに

次はChromiumベースのMicrosoft Edgeを動かせるかどうかやってみたい。
BinaryLocation変えて起動してみるとこんな感じ。できそうな雰囲気はあった。
image.png

※現時点で、ChromeDriverの最新バージョンが75だったので検証保留
image.png

あとは、CefSharpで自作したブラウザとか動かしてみたい気持ちがある。

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

VTuberと一緒に学ぶC#プログラミング入門【#1】~Hello Worldを読んでみたよ

この記事は、以前Mediumに投稿した記事
VTuberと一緒に学ぶC#プログラミング入門【#0】
~Hour of Codeが気付かせてくれるプログラミングの原理
https://link.medium.com/KjYrrl6ZpX
の続きです。

※本記事は筆者(VTuberの技術を理解するためUnityを学んでいる文系のプログラミング初心者です。プログラミングつよつよのVTuber、というわけではありません。そっちを期待した方はごめんなさい…。)の学習の記録になっています。想定している読者は、Hour of Codeをやってみた程度のまったくのプログラミング入門者です。プログラミングの経験が豊富にある方で、記事の内容に間違いを発見された読者の方がいらっしゃいましたら、遠慮なくご指摘いただけますと助かります。

はじめに ~課題の細分化~

slope.png
自分にとって困難な課題に取り組むときは、それをなるべく細かい要素に分けましょう。口に入らない食べ物を細かく切るように、段差を車いすで上がるときにスロープを用いるように、自分が「これならできる」と思えるまで、どこまでも小さい単位に課題を細分化し、その最小単位を少しずつ処理することによって、自分には到底できないと思い込んでいたことが、思いのほか、だんだんとできるようになってきます。大切なのは「できない(のでやらない)」という選択肢を頭から取り除くことです。Unityで動くC#のプログラムを自作できるようになることはとても高い目標ですが、少しずつ上達していける小さい単位の課題を見つけていくことにしましょう。

あわてない。あわてない。

新しいプログラミング言語を学ぶ唯一の道は、
それでプログラムを書いてみることである。

『プログラミング言語C』(第2版)Brian W. Kernighan、Dennis M. Ritchie

C#によるプログラムは、Unityがないと機能しないものではないので、今後ずっとUnityの機能を拡張するものを作るにしても、Unityのない最小の環境で、最小のコードを書くことでC#プログラムが動く様子を見てみることは有益だと思います。ここでは、最も単純で最も有名なプログラムと一般的に言われている『Hello World』(画面に文字を表示するプログラム)が読めるようになる、ということを今回の目標、プログラムを学ぶための最小単位の第1歩として取り組んでみます。

C#による『HelloWorld』のコード
using System;
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");
        }
    }

Hour of Codeのブロックプログラミングによって、JavaScriptというプログラミング言語を小一時間かじった程度の知識でも、Console.WriteLine("Hello World!");という1行が文字を表示する命令なのは何となくわかる気がするのですが、それ以外のブロック(波かっこ{}によって入れ子状に囲まれた範囲)がいろいろあってそこに未知のコードが書いてあるせいで「こんなに短いコードなのに読めない!!」という気持ちになってしまいますね…(なりませんか?)。ともあれ順番に、どういう意味のあるコードなのか調べていきます。

主な参考サイト

C# によるプログラミング入門
https://ufcpp.net/study/csharp/
こちらの入門サイトを主に読んでいきます。目次を見ていると未知の単語がずらっと並んでいて回れ右しそうになるのですが「ごく短いコードが読めるようになる」という目標にそって、若干順番を前後しながら進んでいくことにしました。

プログラムの「実行」とそのための環境

わたしたちがHour of Codeで書いてきたJavaScriptと違って、C#はソースコード(ただのテキストとしての状態のプログラム)をコンパイル(コンピュータが解る形式に変換すること)してから実行するコンパイラ型言語です。以下のページからその大まかなイメージが読み取れます。(このあたりはUnityのためのプログラムを作る際はまた変わってくるので、おおまかな理解で先に進みます。)
https://ufcpp.net/study/csharp/st_compile.html
https://wa3.i-3-i.info/word189.html

ブラウザでコードを書いて実行できるお手軽環境

Wandbox
https://wandbox.org/
C#のプログラムを書いて実行する環境には、Visual Studioのような本格的なものをはじめ様々なものがあるのですが、本格的な開発環境になればなるほど、ごく短いテキストをコンパイルして実行する、というごく単純なことをするためには装備が大げさすぎて、動作も重く、そのせいで何となくやる気が奪われがちな気がします。そこで、今回はもっと手っ取り早く、ブラウザだけでコードを書いて実行できるWandboxを用いて学んでいくことにします。

スクリーンショット-2019-06-.png
記述する言語としてC#を、一番上のmcsコンパイラを選択し、コンパイラ選択ボタンの脇にあるLoad templateをクリックすると、コードを記述するための領域に以下のようなWandbox標準のHelloWorldコードが自動で記述されます。

WandboxテンプレートのHelloWorld
// This file is a "Hello, world!" in C# language by Mono for wandbox.
using System;

namespace Wandbox
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, Wandbox!");
        }
    }
}

// Mono references:
//   http://www.mono-project.com/

// C# language references:
//   https://msdn.microsoft.com/library/618ayhy6.aspx

SS2.png
Run(実行)ボタンをクリックすると、画面に文字(Hello, Wandbox!)が表示されました。これが正しい実行結果です。

Hello Worldコードの比較

以下は、C#をつくったマイクロソフト社の公式ドキュメント( https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-specification/introduction )にある、よりシンプルなHelloWorldのコードです。これをWandboxにコピペして実行すると、同じように「Hello, World」という文字が表示されます。

Microsoft公式のドキュメントにあるHelloWorld
using System;

class Hello
{
    static void Main() {
        Console.WriteLine("Hello, World");
    }
}

ふたつのプログラムは同じ機能をもつものであることがわかりましたが、先に見たWandboxが自動で記述したコードのほうが、いろいろと長い記述になっています。二つのコードの、共通しない部分を調べていきます。

改行、インデント(字下げ)

Microsoft公式のドキュメントにあるHelloWorld(改行、インデントなし)
using System;class Hello{static void Main(){Console.WriteLine("Hello, World");}}

C#のプログラムでは、改行とインデント(字下げ)はソースコードを読みやすくする機能を持つだけで、プログラムそのものには影響しません。よって、すべて削除しても同じようにプログラムは機能します。

コメント

WandboxテンプレートのHelloWorld(部分)
// This file is a "Hello, world!" in C# language by Mono for wandbox.
WandboxテンプレートのHelloWorld(部分)
// Mono references:
//   http://www.mono-project.com/

// C# language references:
//   https://msdn.microsoft.com/library/618ayhy6.aspx

コメント
https://ufcpp.net/study/csharp/st_comment.html
//(スラッシュ2つ)の後に書いた文章をコメントと言います。コードの中に、プログラムの機能に影響を及ぼさないようにしてコードの説明を書くことができます。

namespace(名前空間)

WandboxテンプレートのHelloWorld(部分)
namespace Wandbox
{
(省略)
}

名前空間
https://ufcpp.net/study/csharp/sp_namespace.html
namespaceは、あらかじめプログラムの中身を分類しておく仕組みです。このブロックは、マイクロソフトのコードに存在しないことからも判るように、差し当たり無くても機能しますので、後から詳しく学ぶことにします。

string[] args

WandboxテンプレートのHelloWorld(部分)
static void Main(string[] args)
Microsoft公式のドキュメントにあるHelloWorld(部分)
static void Main() 

WandboxのコードのMain()のカッコの中にのみあるstring[] argsの部分も、無くても同じように機能するので、あとで学ぶことにします。

WandboxテンプレートのHelloWorld(短縮したもの)
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Hello, Wandbox!");
    }
}

これで、Wandboxのコードがマイクロソフトのコードとほぼ同じになりました。以下は、マイクロソフトのHelloWorldコードのみに絞って調べていきます。

コードの不備によるエラー

ここから先は、画面に文字を表示するという機能に影響するコードです。削ってしまうとエラーが起こります。
SS3.png
Hello Worldプログラムの1行目using System;を削除すると上記のように赤字でエラーが表示されます。それ以外の諸々の箇所も、それを削ったことによって諸々のエラーとなります。また、System Mainなどの予約語(C#プログラムの中で決まった意味を持つ文字)は大文字小文字を区別します。大文字で書いてあるところを小文字で書いてしまうと、これもやはりエラーになります。

ということで、私が学ばないといけない最低限のコードはこの8行の中身であることが判明しました。これならばなんとか読み取れそうな気がします。でも、これだけのコードでも、まったく未知の状態からだと、かなり多くの概念を学ばないと意味がわかりません。

コードの構造と「オブジェクト指向」

Hour of Codeで最後に作ったJavaScriptのコード
while (notFinished()) {
  if (isPathForward()) {
    moveForward();
  } else {
    if (isPathRight()) {
      turnRight();
    } else {
      turnLeft();
    }
  }
}

Hour of Codeで書いたJavaScriptは、命令条件に挟まれたものでした。

Microsoft公式のドキュメントにあるHelloWorld
using System;

class Hello
{
    static void Main() {
        Console.WriteLine("Hello, World");
    }
}

しかし、C#のHello Worldのコードは、命令を成立させるために、まったく違う構造を取る仕様になっています。このような書き方をする決まりを定めただけでなく、このように書かないとプログラムが動かないような仕様にしてあるのは、C#がオブジェクト指向であるから、らしいです。

オブジェクト指向とは何なのか…。正直、詳しくはよくわからないです。

オブジェクト指向とは ~深淵を覗いてみる。

新人プログラマに知っておいてもらいたい
人類がオブジェクト指向を手に入れるまでの軌跡

https://qiita.com/hirokidaichi/items/591ad96ab12938878fe1

オブジェクト指向とは何なのか、ということを一生懸命調べていくと、たいへんに奥深い深淵を覗き込むことになります。上記の記事を読んで「ああなるほど」と思えるように、いつかはなりたいものだなあと思いながら、もう少しシンプルな理解が得られる情報に戻ります。

オブジェクト指向とは
https://ufcpp.net/study/csharp/oo_about.html
オブジェクト指向は、プログラムが大きく複雑になってきたときに諸々の非効率が起こらないように、構造が明確な書き方をする、という考え方に則って確立してきたものなのですね。

ではつづいて、一行ずつ、さらに細かく見ていくことにします。

using System;

Microsoft公式のドキュメントにあるHelloWorld(部分)
using System;

英語を直訳すると、システムを使用している。となり、なんとなく機能がわかるような気がしなくもないですが、未知の語と未知の語の組み合わせですので、知ったかぶりしないで調べます。

using

usingを使わないHelloWorld
//using System;を削除
class Hello{
    static void Main(){
        System.Console.Write("Hello World!"); //System.を追加
        }
}

using System;と書くかわりに、Console.Write("Hello World!");の前にSystem.を付け加えると、同じ実行結果になります。逆に言うと、usingによって命令のあるブロックの外に置くことで、命令が増えたときに命令ごとにいちいちSystem.と書かないで済むように省略しているわけです。これをusingディレクティブと言います。

System

System 名前空間(まだ詳しく読まなくて大丈夫です。)
https://docs.microsoft.com/ja-jp/dotnet/api/system?view=netframework-4.8

library.png

C#をはじめとした大規模なプログラム構築に対応した言語は、どのようなプログラムでも使う基本的な機能を効率的に実装するために、ライブラリ(あらかじめ機能単位に分類されて用意してあるいろいろなパーツ)をプログラムの実行時に含めて使うことができるようになっています。(Hello Worldのプログラムでいえば、出力画面に文字を表示する、という機能の具体的な詳細に相当する部分です。)そのおかげで、同じ機能を実現するために、プログラム毎に同じコードをいちいち書く手間が省けます。今後、プログラムを実際に作っていく中では、ほぼ必ず何かしら、このような出来合いのライブラリを用いていくことになります。

;(セミコロン)


https://ufcpp.net/study/csharp/st_variable.html#statement

;(セミコロン)は(プログラムの意味ある単位)を区切るためのものです。人間は改行などによって文の区切りを見分けることができますが、先に書いたようにコンピュータは改行を無視するようになっており、セミコロンがないと文の区切りを判断できないため、コンパイルの際エラーになります。

class Hello{}

たぶん、C#のようなオブジェクト指向のプログラム言語を学ぼうとして、初心者にとっていまひとつよく理解できなくて困り果てる概念のひとつがクラスではないでしょうか。(わたしはそうです。)
class.png
以前、わたしがC#以外の言語を学んでいたころ(何の言語か、思い出せない…。)プログラムに詳しい友人に「クラスってなに?」と質問したところ、帰ってきた答えは「学校のクラスみたいなものだよ」という答えでした。実際、クラスという言葉を日常語として思い浮かべた場合、プログラミングの経験のない人が思い浮かべるのは学校のクラスのイメージですよね。

学校のクラスは集合体です。あるもの(いるひと)として生徒、先生、教室、机、文房具などが含まれ、することとして授業、レクリエーション、などが含まれた概念です。プログラム言語におけるクラスも、同じようにメンバ変数(あるもの)メソッド(すること)を含む集合として定義されます。

igata_model.png

プラトン編-イデア論とクラス/インスタンス
https://www.itmedia.co.jp/im/articles/0506/11/news011.html
クラス
https://ufcpp.net/study/csharp/oo_class.html

一方、いつもの入門サイトをはじめ、多くの解説では、クラスとは「オブジェクトの設計図」とか「オブジェクトの鋳型」などと呼ばれています。クラスを元にして作られた実体がインスタンスである。つまり、インスタンスを生成する元になるものがクラスである、というように説明されています。

ただ、メンバ変数インスタンスはこのHello Worldのコードには出てこないので、ここではまだ説明できません。これらは後で学ぶことにしますが、学校のクラスの概念に当てはめてみると、「教室にあるもの(いるひと)」と「教室ですること」を定めて集めたものがクラスで、実際の4年3組がインスタンス、というような感じになるでしょうか。(現実には学校のクラスという言葉はもうすこし異なる文脈で使われますが。)

Hello Worldのコードでは、Helloクラスのブロックのなかに、すること(処理)が含まれることになります。クラスの中に定義された処理をメソッドと呼びます。Helloは任意に定められたクラスの名前ですが、マイクロソフトのコードのように、そのクラスのことがわかりやすい名前にするのが良いようです。

なぜいきなり命令を書いてはいけないのか

このように書いてはダメなの?
using System;

static void Main() {
    Console.WriteLine("Hello, World");
}

ダメです。

Hello WorldのコードからこのHelloクラスを削除するとプログラムが動作しません。別にクラスに含めなくても、文字を表示する実際の処理だけ記述すれば問題ないような気もしますが、C#のプログラムでは仕様としてそういう書き方はできないことになっています。

C# にはグローバル変数やグローバル メソッドはありません (言語によっては、グローバル変数やグローバル メソッドが存在する場合もあります)。 プログラムのエントリ ポイントである Main メソッドでも、クラスまたは構造体で宣言する必要があります。
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/

クラスに含めずに本文に直接定義する処理をグローバルメソッドと呼びますが、C#のプログラムにそれを作ろうとするとコンパイルの際エラーになります。しかし、メソッドがクラスの中にしか記述出来ないというルールは、あらゆるプログラミング言語で共通して決められているものではありません。

Say ‘Hello World’ in 28 Different Programming Languages
https://excelwithbusiness.com/blog/say-hello-world-in-28-different-programming-languages/
このサイトは様々な言語でHello Worldを記述して集めたものですが、メソッドがクラスの中にしか記述出来ないのはJavaやC#、その派生言語くらいで、あまり多くないことがわかります。

C言語によるHelloWorld
#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
}

ちなみに、C言語のHello Worldもこのようなもので、クラスを定義しなくても動作します。歴史的には、比較的新しい言語でグローバルメソッドが仕様として定義できなくしてあるのは、古い言語でそれを用いたプログラムによっていままでいろいろな問題が起こってきたからのようです。Hello Worldのような極めて短くて機能の限定されたコードでは面倒な記述が増えるだけのように思えますが、これも後々プログラムが大きく複雑になってきたときのためにあるものとして考える必要があるようです。

static void Main(){}

Microsoft公式のドキュメントにあるHelloWorld(部分)
static void Main() {
Mainメソッドの処理内容)
}

Mainメソッド(することの主なもの)を定義します。Hello Worldでは文字を表示する命令がひとつ含まれるだけなので実感が湧かないと思いますが、Mainメソッドは、C#のプログラムにとって特別な意味があるメソッドです。staticvoidはMainメソッドの性質を示すもので修飾子(しゅうしょくし)と呼びます。Mainメソッドにおいては、staticは他のものに変更できませんが、voidは別の選択肢があります。

Main()

Mainはメソッドの名前ですが、クラス名Helloと違って自由に名づけられる名前ではなく、変更して実行しようとするとエラーになります。Mainメソッドはここからすべての処理を開始するよう、あらかじめC#の仕様として定められている必須のメソッドです。これをエントリーポイントと言います。

エントリーポイント
https://ufcpp.net/study/csharp/structured/miscentrypoint/

()(括弧)の中には引数(ひきすう:メソッドに渡して処理を行う値)を入れますが、何も書かなくてもHello Worldプログラムは機能するので後で学びます。ただし、引数が入らないからといって括弧も外してしまうとエラーになりますので、引数がない場合は、中に何も入っていない括弧だけ書くことになります。

メソッド(すること)

プログラム内ですることにあらかじめ名前をつけてまとめておくのは、プログラムの別の場所で同じことをするとき繰り返し同じコードを書かなくて済むようにするためです。学校のクラスに例えてみると、

【そうじをする】
・机といすを片付ける
・床をほうきで掃く
・床をぞうきんで拭く
・机といすを元の位置にもどす

別の場所では

普段は、5時間目が終わったら【そうじをする】
半日登校の日は、3時間目が終わったら【そうじをする】
大掃除のときは【そうじをする】ほか、窓ふきをする

と、(すること)の名前だけ書けば済むようにします。こうしておけば、「半日登校の日だけぞうきんがけを忘れた!」というような間違いが起こりにくくなります。

static

Mainメソッドが静的メンバーであることを示します。

静的メンバー(static member)とは、 特定のインスタンスにではなく、クラスに属するフィールドやメソッドのことです。
https://ufcpp.net/study/csharp/oo_static.html

さきほどちょっとお話しましたが、MainメソッドしかないHello Worldのプログラムではインスタンスを生成する過程が登場しないので、Mainメソッドがインスタンスにではなくクラスに属している、ということがどういう意味なのか具体的にイメージがわきません。これはインスタンスの生成について詳しく学ぶ際にあわせて学ぶことにします。しかし、

Main は static である必要があります
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/main-and-command-args/

Mainメソッドはstaticでないといけない仕様だそうなので、static以外に書き換えたりstaticを削除したりするとエラーになります。

void

戻り値(もどりち:メソッドから返ってくる処理の結果の値)の(かた:データの種類)を定義します。

戻り値
https://wa3.i-3-i.info/word1441.html

SS2-2.png
WandboxでHelloWorldを実行すると、実行結果として表示される「Hello World!」の文字列の次に、ピンクの文字で0という値が表示されますが、これがMainメソッドの戻り値です。プログラムが正常に実行されると0、エラーの場合1が表示されます。緑色の文字のStartFinishもそうですが、Wandboxが気を利かせて表示してくれているだけで、実際のコンソールアプリケーションでC#を実行した場合には表示されません。void戻り値を持たないメソッドであることを示しますので、Wandboxで0と表示されるのは特別な仕様です。

Mainメソッドの戻り値にはこのvoid型と、整数(英:integer)の戻り値を返すint型の2種類が定義できます。

int型
https://wa3.i-3-i.info/word14966.html

戻り値が整数(int型)のMainメソッド
using System;
class Hello{
    static int Main(){
        Console.Write("Hello World!");
        return 3; //戻り値を返す命令
    }
}

たとえば、このように記述すると、戻り値3が実行画面に表示されます。いずれにしても、画面に文字を表示する機能しか持たないHelloWorldのプログラムではMainメソッドの戻り値は特に用いられないので、これも後で詳しく学ぶことにします。

Console.Write("Hello World!");

こうしてわたしたちは長い長い遠回りを経てようやっと、中心になる命令文の部分にたどりつきました!

using System;の解説で触れましたが、この行はSystem.Console.Write("Hello World!");System.using System;によって省略したものです。つまり、System名前空間にあらかじめ用意してあるConsoleクラスのWriteメソッドを用いて、Hello World!と表示する、ということです。

Console

System名前空間のConsoleクラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.console?view=netframework-4.8

コンソール(プログラムの実行結果が表示される黒背景の画面)を扱うための入力(キーボードで画面に文字を打つ操作など)や出力(今回のHello Worldのようにプログラムの実行結果を表示する操作など)、エラー(プログラムの間違いをコンソールに表示する操作など)に関する出来合いのコードがまとめられているクラスです。

Write()

ConsoleクラスのWriteメソッド
https://docs.microsoft.com/ja-jp/dotnet/api/system.console.write?view=netframework-4.8

コンソールに改行なしで実行結果を出力します。文末で改行する場合はWriteメソッドの代わりにWriteLineメソッドを用います。

WriteメソッドとWriteLineメソッド
using System;
class Hello{
    static void Main(){
        Console.Write("指定した書式情報を使用して、指定したオブジェクトのテキスト表現と可変長パラメーター リストを標準出力ストリームに書き込みます。");
        Console.WriteLine("指定したデータを標準出力ストリームに書き込み、");
        Console.WriteLine("続けて現在の行終端記号を書き込みます。");
    }
}

上記のコードを実行すると以下のようになります。一行目が画面端で折り返しされているのはWandboxの設定によるもので、プログラム側の改行によるものではありません。
スクリーンショット-2019-10-.png

文字列

コンピュータが理解するための命令ではなく、人間が読むために入出力する文字の集まったデータを文字列(もじれつ)と呼びます。文字列は、命令文などと区別するために"(ダブルクオーテーション)で挟みます。

文字列であることを示すため「"」で挟む
Console.Write("コンピュータはダブルクオーテーションで挟んだ部分を文字列として認識します。");
Console.Write(ダブルクオーテーションで挟まないとコンピュータが正しく認識できず、エラーになります。);

上記のQiitaフォーマットのコード表記では文字列は自動で青字で表示されるようになっています。2行目のように書くとエラー(赤い点線のアンダーラインによって、文法が間違いであることが示されています。)

おわりに

以上で、C#のHello Worldプログラムをすべて読み終わりました!まだ十分に把握できたとはいえない部分が残ってしまっていますが、最初の「まったく意味がわからない」という状態からは抜けることができたのではないでしょうか。まだまだ道のりは長く険しいですが、まったく読めなかったころと比べれば確実に進歩していると思いますので、自信を持ってもらえたらうれしいです。『VTuberと一緒に学ぶ』と言いつつ今回はVTuberのVの字も出てこない記事になってしまいましたが、次回は、いよいよ実際にUnityを用いたプログラミングを学んでいこうと思います!(筆者は年間数日ほどある極めてまれな「暇なとき」を用いてこの記事を書いているので、次もまた1年先とかかもしれませんが、今後もなるべく古くなりにくい初歩的なトピックを選んで書いていこうと思います。)

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

【Unity】Cloud Diagnostics と NCMBが競合してAndroidでビルドエラー Program type already present: com.nifty.cloud.mb.ncmbgcmplugin.GCMInit$1

Cloud Diagnosticsとは

cloud-diagnostics
Setting up Cloud Diagnostics

エラー

CommandInvokationFailure: Gradle build failed. 
/Applications/Unity/Hub/Editor/2018.4.0f1/PlaybackEngines/AndroidPlayer/Tools/OpenJDK/MacOS/bin/java -classpath "/Applications/Unity/Hub/Editor/2018.4.0f1/PlaybackEngines/AndroidPlayer/Tools/gradle/lib/gradle-launcher-4.6.jar" org.gradle.launcher.GradleMain "-Dorg.gradle.jvmargs=-Xmx4096m" "assembleRelease"
stderr[
D8: Program type already present: com.nifty.cloud.mb.ncmbgcmplugin.GCMInit$1
FAILURE: Build failed with an exception.

(以下割愛)

Cloud Diagnosticsをインストールしてビルドしてみるとエラー。
非常に長いエラーメッセージが表示されますが、よく見るとncmbgcmpluginに含まれるGCMInit$1が重複してますよーって書いてある。

環境

MacOSX 10.14.5
Unity 2018.4.0f1
NCMB Version 2.0
Unity User Reporting 0.2.4-preview

解決方法

NcmbGcmPlugin.aarに含まれるclasses.jarに含まれるGCMInit$1.classを取り除きます。

手順

 Finderでプロジェクトを開き、NcmbGcmPlugin.aarを検索する

MyProject/Assets/Plugins/Android/NcmbGcmPlugin.aar

 NcmbGcmPlugin.aarの拡張子を.zipに変えて解凍する

image.png

 classes.jarの拡張子を.zipに変えて解凍する

image.png

classes/com/nifty/cloud/mb/ncmbgcmplugin/GCMInit$1.classを削除

image.png

classes.jarに圧縮する

image.png

image.png

image.png

image.png

image.png

image.png

NcmbGcmPlugin.aarに圧縮する

image.png

image.png

image.png

image.png

image.png

これをやってもエラーになる場合

他にもNcmbGcmPlugin.aarがあるかもしれないと思ってFinderで検索するとTempフォルダ配下にもあります。Tempフォルダは消しても新しく作り直されるので、フォルダごと削除して再度ビルド。
うまくいきました。

image.png

おまけ

Cloud Diagnosticsはよさそう!

Unityダッシュボードでレポートを見れますが、プロジェクトに入れただけなのに過去に遡って見れました。
(あれ?前から見れた?)
すごい、こんな詳しく...
てか、わたしのアプリエラー多すぎ....?!
(あとエディタのエラーも混ざってるっぽい?)

image.png

image.png

エラー詳細
image.png

無料版だと過去7日間しか保持されないため、これはPlusにしたくなりますね。
image.png

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