- 投稿日:2019-06-07T22:38:02+09:00
対象のリストからフィルタに含まれない要素を除外する (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); } }
- 投稿日:2019-06-07T22:16:38+09:00
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!"); } }
- 投稿日:2019-06-07T20:13:43+09:00
.NET Core プロジェクトによる元号(和暦)表示の注意点
この記事は
数ヶ月前に散々騒がれていた新元号とはまた別の話です。
具体的には、元号を アルファベット一文字 で表記する際の注意点になります。
前提条件
環境は下記の通りになります。
- Windows 10 1809 Build 17763.475
- CentOS 7.6.1810
- どちらも .NET Core 3.0.100-preview5-011568
元号表示に利用するコード
基本的な部分は一般的な元号表記と同様に
System.Globalization
名前空間のCultureInfo
とJapaneseCalendar
を使用しています。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
のソースコードを見てみましょう。簡単にすると、以下の手順で処理しています。
EraNames
に引数がないか探す。AbbreviatedEraNames
に引数がないか探す。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 = 4CentOS平成 = 4 平 = 4 Heisei = 4 H = -1……やっぱり仲間はずれがいましたし、さらに環境によっては仲間はずれにされるのが異なっているようですね。
探す対象になっている3つの配列はそれぞれ直訳すると 「元号」「省略された元号」「省略された英語の元号」 となりますし、レジストリから取得した際の出力結果のほうが意味として正しいように感じます。
まとめ
- .NET Core はクロスプラットフォームだけど、なんでも同じように動くわけではない。(少なくとも現在は)
- この記事の内容に関しては改元の話で盛り上がるよりも前から遭遇していたし解決もしたが、いつかまとめようと思っていたら改元の話題が始まって終わっていた。
Qiita初投稿なのでなにか書き方が間違っていたりした場合は教えてください。もちろん、技術的に間違っている場合もどんどんご意見をください。
- 投稿日:2019-06-07T18:09:30+09:00
[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
に、
「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
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
>「プロパティの設定」
- 投稿日:2019-06-07T16:02:28+09:00
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" }これで大丈夫そうです。
- 投稿日:2019-06-07T15:51:51+09:00
読み取り専用に公開するパターン
外部からは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(); コンパイルエラー }
- 投稿日:2019-06-07T15:03:44+09:00
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の動きを見ながらそれっぽく動くようにしたつもりですけど、挙動に変なところがあるかもしれません。お許しください。
- 投稿日:2019-06-07T13:33:03+09:00
SeleniumのChromeDriverでGoogle Chrome以外のブラウザを動かす
はじめに
ブラウザ自動テストのデファクトスタンダードである
Selenium
そのChromeDriver
を使ってGoogle Chrome
以外のブラウザを操作することができるのか?
という遊び。動かせた時の感動は省略
やったこと
Chrome.exe
ここから取得:https://download-chromium.appspot.com/
そして適当なパスに配置する
※後述するが、取り直すことになった…NuGet
ソース
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
変えて起動してみるとこんな感じ。できそうな雰囲気はあった。
※現時点で、ChromeDriverの最新バージョンが75だったので検証保留
あとは、CefSharpで自作したブラウザとか動かしてみたい気持ちがある。
- 投稿日:2019-06-07T09:52:02+09:00
VTuberと一緒に学ぶC#プログラミング入門【#1】~Hello Worldを読んでみたよ
この記事は、以前Mediumに投稿した記事
VTuberと一緒に学ぶC#プログラミング入門【#0】
~Hour of Codeが気付かせてくれるプログラミングの原理
https://link.medium.com/KjYrrl6ZpX
の続きです。※本記事は筆者(VTuberの技術を理解するためUnityを学んでいる文系のプログラミング初心者です。プログラミングつよつよのVTuber、というわけではありません。そっちを期待した方はごめんなさい…。)の学習の記録になっています。想定している読者は、Hour of Codeをやってみた程度のまったくのプログラミング入門者です。プログラミングの経験が豊富にある方で、記事の内容に間違いを発見された読者の方がいらっしゃいましたら、遠慮なくご指摘いただけますと助かります。
はじめに ~課題の細分化~
自分にとって困難な課題に取り組むときは、それをなるべく細かい要素に分けましょう。口に入らない食べ物を細かく切るように、段差を車いすで上がるときにスロープを用いるように、自分が「これならできる」と思えるまで、どこまでも小さい単位に課題を細分化し、その最小単位を少しずつ処理することによって、自分には到底できないと思い込んでいたことが、思いのほか、だんだんとできるようになってきます。大切なのは「できない(のでやらない)」という選択肢を頭から取り除くことです。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を用いて学んでいくことにします。
記述する言語として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
Run(実行)ボタンをクリックすると、画面に文字(Hello, Wandbox!)が表示されました。これが正しい実行結果です。Hello Worldコードの比較
以下は、C#をつくったマイクロソフト社の公式ドキュメント( https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-specification/introduction )にある、よりシンプルなHelloWorldのコードです。これをWandboxにコピペして実行すると、同じように「Hello, World」という文字が表示されます。
Microsoft公式のドキュメントにあるHelloWorldusing 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コードのみに絞って調べていきます。
コードの不備によるエラー
ここから先は、画面に文字を表示するという機能に影響するコードです。削ってしまうとエラーが起こります。
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公式のドキュメントにあるHelloWorldusing 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.8C#をはじめとした大規模なプログラム構築に対応した言語は、どのようなプログラムでも使う基本的な機能を効率的に実装するために、
ライブラリ
(あらかじめ機能単位に分類されて用意してあるいろいろなパーツ)をプログラムの実行時に含めて使うことができるようになっています。(Hello Worldのプログラムでいえば、出力画面に文字を表示する、という機能の具体的な詳細に相当する部分です。)そのおかげで、同じ機能を実現するために、プログラム毎に同じコードをいちいち書く手間が省けます。今後、プログラムを実際に作っていく中では、ほぼ必ず何かしら、このような出来合いのライブラリを用いていくことになります。;(セミコロン)
文
https://ufcpp.net/study/csharp/st_variable.html#statement
;
(セミコロン)は文
(プログラムの意味ある単位)を区切るためのものです。人間は改行などによって文の区切りを見分けることができますが、先に書いたようにコンピュータは改行を無視するようになっており、セミコロンがないと文の区切りを判断できないため、コンパイルの際エラーになります。class Hello{}
たぶん、C#のようなオブジェクト指向のプログラム言語を学ぼうとして、初心者にとっていまひとつよく理解できなくて困り果てる概念のひとつが
クラス
ではないでしょうか。(わたしはそうです。)
以前、わたしがC#以外の言語を学んでいたころ(何の言語か、思い出せない…。)プログラムに詳しい友人に「クラスってなに?」と質問したところ、帰ってきた答えは「学校のクラスみたいなものだよ」という答えでした。実際、クラス
という言葉を日常語として思い浮かべた場合、プログラミングの経験のない人が思い浮かべるのは学校のクラスのイメージですよね。学校のクラスは集合体です。あるもの(いるひと)として生徒、先生、教室、机、文房具などが含まれ、することとして授業、レクリエーション、などが含まれた概念です。プログラム言語におけるクラスも、同じように
メンバ変数
(あるもの)とメソッド
(すること)を含む集合として定義されます。プラトン編-イデア論とクラス/インスタンス
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#のプログラムにとって特別な意味があるメソッドです。static
とvoid
は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
WandboxでHelloWorldを実行すると、実行結果として表示される「Hello World!」の文字列の次に、ピンクの文字で0
という値が表示されますが、これがMainメソッドの戻り値です。プログラムが正常に実行されると0
、エラーの場合1
が表示されます。緑色の文字のStart
とFinish
もそうですが、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の設定によるもので、プログラム側の改行によるものではありません。
文字列
コンピュータが理解するための命令ではなく、人間が読むために入出力する文字の集まったデータを
文字列
(もじれつ)と呼びます。文字列は、命令文などと区別するために"
(ダブルクオーテーション)で挟みます。文字列であることを示すため「"」で挟むConsole.Write("コンピュータはダブルクオーテーションで挟んだ部分を文字列として認識します。"); Console.Write(ダブルクオーテーションで挟まないとコンピュータが正しく認識できず、エラーになります。);上記のQiitaフォーマットのコード表記では文字列は自動で青字で表示されるようになっています。2行目のように書くとエラー(赤い点線のアンダーラインによって、文法が間違いであることが示されています。)
おわりに
以上で、C#のHello Worldプログラムをすべて読み終わりました!まだ十分に把握できたとはいえない部分が残ってしまっていますが、最初の「まったく意味がわからない」という状態からは抜けることができたのではないでしょうか。まだまだ道のりは長く険しいですが、まったく読めなかったころと比べれば確実に進歩していると思いますので、自信を持ってもらえたらうれしいです。『VTuberと一緒に学ぶ』と言いつつ今回はVTuberのVの字も出てこない記事になってしまいましたが、次回は、いよいよ実際にUnityを用いたプログラミングを学んでいこうと思います!(筆者は年間数日ほどある極めてまれな「暇なとき」を用いてこの記事を書いているので、次もまた1年先とかかもしれませんが、今後もなるべく古くなりにくい初歩的なトピックを選んで書いていこうと思います。)
- 投稿日:2019-06-07T05:21:01+09:00
【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
に変えて解凍する
classes.jar
の拡張子を.zip
に変えて解凍する
classes/com/nifty/cloud/mb/ncmbgcmplugin/GCMInit$1.class
を削除
classes.jar
に圧縮する
NcmbGcmPlugin.aar
に圧縮するこれをやってもエラーになる場合
他にもNcmbGcmPlugin.aarがあるかもしれないと思ってFinderで検索するとTempフォルダ配下にもあります。Tempフォルダは消しても新しく作り直されるので、フォルダごと削除して再度ビルド。
うまくいきました。おまけ
Cloud Diagnosticsはよさそう!
Unityダッシュボードでレポートを見れますが、プロジェクトに入れただけなのに過去に遡って見れました。
(あれ?前から見れた?)
すごい、こんな詳しく...
てか、わたしのアプリエラー多すぎ....?!
(あとエディタのエラーも混ざってるっぽい?)