echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Xamarin】ペアリング済のBluetoothデバイスの情報を取得する。

やりたいこと

  • (今も愛用中)のガラケー購入時にBluetoothスピーカーをもらったが、リモコンがなく、音量調整が面倒。

  • iTunesは音量調整出来るが、微妙な音量調節が難しい。(特に手が大きいと)

  • Androidでできるアプリを持ってない。→ならXamarinで作ってみようか。※出来るとは言ってない

ハマった?事

  • 最初は、下記の「Bluetooth LE Plugin for Xamarin」を使おうとしたが、「ペアリング済の機器情報」を取得する方法が分からなかった。(あったならすいません...)

  • Bluetoothの「UUID」は「機器ごと」に個別ではなく「サービス/プロファイル」ごとに個別だった。(同じ「スピーカー」ならUUIDは同じだが、それを知らずにハマった…いい勉強になりました。)

参考にした情報

ソース&結果

※すいません。時間の関係で、ソース&結果のみ表示します。
(詳細は後日追記します。)

■SearchDevicePage.xaml(VolChanger.View)

<?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="VolChanger.View.SearchDevicePage">
    <ContentPage.Content>
        <StackLayout>
            <ActivityIndicator x:Name="AILoading" IsVisible="{Binding IsSearching}" IsRunning="{Binding IsSearching}" />
            <Button x:Name="ButtonSearch" Text="Bluetoothデバイスを検出する" Command="{Binding GetDevicesCommand}" />
            <ListView x:Name="ListViewDevices" ItemsSource="{Binding DeviceList}">

                <!--ここに ItemTemplate を追加-->
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextCell Text="{Binding Name}" Detail="{Binding Detail}"/>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

■SearchDevicePage.xaml.cs(VolChanger.View)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using VolChanger.View;
using VolChanger.ViewModel;

using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;

// using Reactive.Bindings;
// using Reactive.Bindings.Extensions;

namespace VolChanger.View
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class SearchDevicePage : ContentPage
    {
        private readonly SearchDeviceViewModel vm;

        public SearchDevicePage ()
        {
            InitializeComponent ();
            this.vm = new SearchDeviceViewModel();
            BindingContext = vm;

            // ButtonSearch.Clicked += ButtonSearch_Clicked;
        }
    }
}

■SearchDeviceViewModel.cs(VolChanger.ViewModel)

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

using VolChanger.View;
using VolChanger.ViewModel;
using VolChanger.Model;

using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;
using Xamarin.Forms;
using Plugin.BLE.Abstractions.EventArgs;

namespace VolChanger.ViewModel
{
    public class SearchDeviceViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<BTDevice> DeviceList { get; set; } 
        public Command GetDevicesCommand { get; }
        public event PropertyChangedEventHandler PropertyChanged;
        private static PropertyChangedEventArgs SearchPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(IsSearching));

        bool isSearching = false;
        public bool IsSearching
        {
            get { return isSearching; }
            set
            {
                if(isSearching == value) { return; }

                isSearching = value;
                OnPropertyChanged(nameof(IsSearching));

                // CanExecute (このコマンドが実行可能かどうか)を更新
                GetDevicesCommand.ChangeCanExecute();
            }
        }

        public SearchDeviceViewModel()
        {
            DeviceList = new ObservableCollection<BTDevice>();

            GetDevicesCommand = new Command(
                () => GetDevices(), // 実行する処理
                () => !IsSearching); // このコマンドが実行可能の時にtrueを返す
        }

        private void GetDevices()
        {
            if (IsSearching)
                return;
            
            try
            {
                IsSearching = true;

                DeviceList.Clear();
                
                IBTManager manager = DependencyService.Get<IBTManager>();
                var list = manager.GetBondedDevices();

                // await Adapter.StartScanningForDevicesAsync();
                if(list != null && list.Count > 0)
                {
                    foreach(var dev in list)
                    {
                        DeviceList.Add(dev);
                    }
                }
                else
                { 
                    Application.Current.MainPage.DisplayAlert("VolChanger", "ペアリング済デバイスが見つかりませんでした。", "OK");
                }
            }
            catch (Exception e)
            {
                Application.Current.MainPage.DisplayAlert("Error!", e.Message, "OK");
            }
            finally
            {
                IsSearching = false;
            }
        }

        void OnPropertyChanged([CallerMemberName] string name = null) =>
            PropertyChanged?.Invoke(this, SearchPropertyChangedEventArgs);
    }
}

■BTDevice.cs(VolChanger.Model)

using System;
using System.Collections.Generic;
using System.Text;

namespace VolChanger.Model
{
    public class BTDevice
    {
        public string Name { get; set; }
        public string Uuid { get; set; }
        public string Adress { get; set; }
        public string Detail { get; set; }
    }
}

■IBTManager.cs(VolChanger.Model)

using System;
using System.Collections.Generic;
using System.Text;

using VolChanger.Model;

namespace VolChanger.Model
{
    public interface IBTManager
    {
        List<BTDevice> GetBondedDevices();
    }
}

■BTManager.cs(VolChanger.Android)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using VolChanger.Model;
using Android.Bluetooth;

using Xamarin.Forms;

[assembly: Dependency(typeof(VolChanger.Droid.BTManager))]

namespace VolChanger.Droid
{
    public class BTManager: IBTManager
    {
        
        public List<BTDevice> GetBondedDevices()
        {
            // アダプター作成
            BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter; 

             // ペアリング済みデバイスの取得
            var listBondedDevices = adapter.BondedDevices;              
            var result = new List<BTDevice>();

            if (listBondedDevices != null && listBondedDevices.Count > 0)
            {
                foreach (var device in listBondedDevices)
                {
                    var btDevice = new BTDevice();
                    btDevice.Uuid = device.GetUuids().FirstOrDefault().ToString();

                    // Bluetoothスピーカーのみ取得する。
                    if (btDevice.Uuid.Substring(4, 4).ToUpper() == "110A" ||
                        btDevice.Uuid.Substring(4, 4).ToUpper() == "110B")
                    {
                        btDevice.Name = device.Name;
                        btDevice.Adress = device.Address;
                        btDevice.Detail = string.Format("MACアドレス:{0}", btDevice.Adress);
                        result.Add(btDevice);
                    }
                }
            }
            return result;
        }
    }
}

f:id:Makky12:20181201191328j:plain
結果(MACアドレスは一部編集で消してます)

あとは「音量の調整」なんですが、ここからが本当に難しいところでしょうね。

...まあ、それは旅行から帰ってきてからの続き、ということで。

【Linux】Ubuntu Mateを新規にインストールする

経緯

現在、Windows7のLet's Noteを持っているんですが、

panasonic.jp

  • Windows7のサポート終了(2019年1月)まで、あと約1年しかない。
  • 32bitマシンだけど、Windows10をインストールしても大丈夫?
  • それ以前に、まずWindows10がまともに動作するかも怪しい...

ということで、思い切って以前から用意しようと思っていた、Linuxをインストールすることにしました。

なぜUbuntuではないのか?

  • Ubuntu公式サイトに、現在のLTS最新版(18.04)の32bit版isoファイルがない。(64bitのみ。ただしインストール自体は32bitでも可能。)
  • Ubuntu公式が次のバージョンから、正式に32bitはサポートしない模様。

なので、ここはUbuntuではなく、32bit版isoファイルのDLが可能な、フレーバーをインストールすることに。

Ubuntu Mateを選んだ理由

  • カスタマイズ性が良さそうだった
  • 安定性・高速性、両方でバランスよさそうだった。
  • 要求スペックが低かった。(重要)

インストール方法

公式サイトから、*.isoファイルを入手する。
※torrent/直接DL、両方の形式がある。どちらでもOK。

ubuntu-mate.org

*.isoファイルをDVD・USBなどに焼く。(今はもう「焼く」って言わないのかも...)

※DVDでもUSBでも「起動ディスク」として利用できるならどちらでもOK。(僕は対象PCにDVDドライブがなかったので、USBにしました。)
※USBへのイメージファイル書き込みソフトは、「USB ISO 書き込み」等でググればたくさん出てくるので、お好きなものを。
BIOSUEFIなどの起動設定を変えるのを忘れないように。

インストール

てか、スクショ取り忘れたので、画像がない... 下記ページが参考になります。

  • 先述のDVD・USBをDVDドライブやUSBポートに入れた状態で、PCを起動すれば、インストーラが起動する。
  • 完全インストールならば「Install Ubuntu Mate」を選択する。
    ※「Try」の方は試用版の実行で、インストールはしません。とりあえずお試しの場合はこちらを。
  • Ubuntu Mateのインストール準備」画面では、「インストール中にアップデートを...」と「サードパーティー製ソフトのインストール」を両方チェックすると、面倒くさくない(かも)
  • 「インストールの種類」では、
    デュアルブートやら仮想環境のように、「既存OSを残す」場合は「既存のOSを残し、...」を選択する。
    クリーンインストールする場合は「ディスクを削除して...」を選択。(当然、前のOS&データはすべて削除されるので、ここだけは注意!)
  • 他にも設定項目があるが、あとは「言語選択」「地域選択」「ユーザー情報」等、特に問題ない項目なので割愛。

インストール完了したら

といっても、インストール完了したら、あとは再起動後にユーザー名&パスワードを入力すれば、起動するはず。

とりあえず、あとはお好みで...なのだが、デスクトップレイアウトだけでも、お好みで変えた方がいいかも。

デスクトップレイアウトは、「設定」-「MATE Tweak」から変更可能。 色々ありますが、下記2つあたり押さえておけばWin/Mac、どちらのユーザーでもOKかな?

  • Cupetino:Macっぽいドック&トップパネルがあるレイアウト。
  • RedMond:Windowsっぽい、左下に検索可能なメニューがある。

参考:

f:id:Makky12:20181124192554j:plain

以上、簡単ですが、紹介までに。

Micorosoft Cognitive Serviceの「Face API」をXamarin.Formsで使用する

概要

昨日(2018/11/17)、「初心者向けXamarinハンズオン」に参加したのですが、そこMicrosoft社の「Azure Face API」(顔検出・認識・分析API)を試してきたので、メモ。

イベントページ:https://jxug.connpass.com/event/96679/

※すでに終了してます。

使用方法

公式サイトから、下記いずれかのアカウントを取得する。

※この記事の内容は、「7日間試用版」で実施。

公式サイト:https://azure.microsoft.com/ja-jp/services/cognitive-services/face/

コーディング

公式サイトの左側ツリーの「クイックスタート」内に記載されている、

上記サンプルコードのうち、該当する方を参考にコーディングする。

※本記事は「クライアントSDK使用」にて実施。

サンプルソース

下記を参照。

※下記ソースは、「初心者向けXamarinハンズオン」の下記「本家リポジトリ」の

  • HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Shared/View/DetailsPage.xaml.cs

の内容を、「手順書」の内容を基に編集したものです。

本家リポジトリhttps://github.com/jxug/dev-days-labs-ja/tree/master/hands-on

手順書:https://github.com/jxug/dev-days-labs-ja/tree/master/hands-on

※コメントが記載してない箇所は、「Xamarinハンズオン」に関係しますが「Face API」には直接関係ない処理です。

using System;
using DevDaysSpeakers.Model;
using Xamarin.Essentials;
using Xamarin.Forms;

// Face API関連のインポート
using Microsoft.Azure.CognitiveServices.Vision.Face;
using Microsoft.Azure.CognitiveServices.Vision.Face.Models;

using System.Threading.Tasks;
using System.Collections.Generic;

namespace DevDaysSpeakers.View
{
    public partial class DetailsPage : ContentPage
    {
        // エントリポイント
        private const string faceEndpoint =
            "https://westcentralus.api.cognitive.microsoft.com";

        // 取得したキー(キー1またはキー2)
        private const string subscriptionKey = "(Input Your Key)";

        // 取得したい(顔の)情報を配列で設定。
        private static readonly FaceAttributeType[] faceAttributes =
            { FaceAttributeType.Age, FaceAttributeType.Gender, FaceAttributeType.Emotion };

        readonly Speaker speaker;

        public DetailsPage(Speaker speaker)
        {
            InitializeComponent();

            SetActivityIndicatorProperty(false);

            this.speaker = speaker;
            BindingContext = this.speaker;

            ButtonSpeak.Clicked += ButtonSpeak_Clicked;
            ButtonWebsite.Clicked += ButtonWebsite_Clicked;
            ButtonDetectFace.Clicked += ButtonDetectFace_Clicked;
        }

        private async void ButtonSpeak_Clicked(object sender, EventArgs e)
        {
            await TextToSpeech.SpeakAsync(this.speaker.Description);
        }

        private void ButtonWebsite_Clicked(object sender, EventArgs e)
        {
            if (speaker.Website.StartsWith("http"))
                Device.OpenUri(new Uri(speaker.Website));
        }

        // 「顔を検出」ボタンクリック処理
        private async void ButtonDetectFace_Clicked(object sender, EventArgs e)
        {
            // FaceClientクラスの設定
            var faceClient = new FaceClient(
                new ApiKeyServiceClientCredentials(subscriptionKey),
                new System.Net.Http.DelegatingHandler[] { });
            faceClient.Endpoint = faceEndpoint;

            // インターネット上の顔画像ファイルのパス。(speaker.Avaterにパス情報が格納されています。)
            var remoteImageUrl = speaker.Avatar;

            // ローカル上の顔画像ファイルのパス
            // var localImagePath = @"<LocalImage>";

            try
            {
                SetActivityIndicatorProperty(true);

                // リモートの顔画像から情報を取得。
                // (ローカルの場合は「DetectWithStreamAsync()」メソッドを使用。)
                IList<DetectedFace> faceList = await faceClient.Face.DetectWithUrlAsync(
                    remoteImageUrl, true, false, faceAttributes);

                // 取得できなかった場合
                if (faceList.Count == 0 || faceList == null)
                    return;

                SetActivityIndicatorProperty(false);

                // 取得できた場合、そこから情報(年齢・性別・感情(幸福度))を取得し、メッセージボックスを表示。
                var age = faceList[0].FaceAttributes.Age;
                var gender = faceList[0].FaceAttributes.Gender;
                var emotion = (faceList[0].FaceAttributes.Emotion.Happiness * 100.000).ToString();

                var message = string.Format("年齢:{0}、性別:{1}、幸福度:{2} %", age, gender, emotion);
                await DisplayAlert("FaceAPI", message, "OK");
            }
            catch (APIErrorException exApi)
            {
                throw new Exception(exApi.Message.ToString());
            }
            catch (Exception ex)
            {
                await DisplayAlert("FaceAPI Exception", ex.ToString(), "OK");
            }
            finally
            {
                SetActivityIndicatorProperty(false);
            }
        }

        private void SetActivityIndicatorProperty(bool value)
        {
            AILoading.IsRunning = value;
            AILoading.IsVisible = value;
        }
    }
}

ポイントとしては、

※下記のインポート(using)

※取得したい(顔の)情報(FaceAttributeType)をFaceAttributeType配列で設定。

FaceAtteributeについては、下記ページが参考になります。

参考ページ:Microsofrt AzureのFace APIを使って笑顔をAIに判断してもらおう

※エントリポイント、キーの設定

※リモート/ローカル画像に応じたメソッド実施(DetectWithUrlAsync/DetectWithStreamAsync)

でしょうか。

ハマった点

  • Azureアカウントの場合、EntryPointのURLは公式ソースと違い「国・地域」を反映させたものじゃないとダメ。 (7日間試用版の場合、公式ソース同様「westcentralus」で問題ないです。)

  • 発行されるEntryPointのURLの末尾に「Face/(バージョン)」と書かれているが、実際には「Face/(バージョン)」は不要。(ドメイン名まででよい。てか、公式ソースがそうなってるし...ちゃんと読めよ...)

実行結果

f:id:Makky12:20181118185932j:plain

てか、Face API(Cognitive Service)に限らず、Azureとか、どんどん触れていきたいと思いました。

DataTableからListにデータを変換する方法

やりたいこと

  • DataTableの内容(主にデータベースのデータ)に対しても、ListみたいにLinqで扱いたい。

理由

  • ループで1行ずつList.Add()すれば出来るけど、Enumerable.ToList()みたいに スマートに実現出来ないか? (コードもシンプルになるし)
  • 何だかんだ言っても、.net製の業務システムではまだまだDataTable(DataSet)が 現役なので、 変換ができるようになっておくと便利。
  • DataTableとListでは、後者の方が処理が早いみたいなので。(※)

※参照:意外と遅い DataTable 、なので List を使うと 5 倍早くなる

いきなり結論

「AsEnumerable()」メソッドを使えばOK。(.net Framework3.5以降)
このメソッドを実施するとIEnumerableオブジェクトが戻り値として返されるので、 あとはToList()を実行すれば、Listに変換できる。

サンプルソース

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConvDs2ListCs
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 今回はDataTable,DataRowCollectionは自前で用意。
            var dt = new DataTable();

            dt.Columns.Add("ORDER_NO");
            dt.Columns.Add("USER_NO");
            dt.Columns.Add("COUNTRY_CODE");
            dt.Columns.Add("PREF_CODE");

            var dr = dt.NewRow();

            dr["ORDER_NO"] = "0001";
            dr["USER_NO"] = "AAAA";
            dr["COUNTRY_CODE"] = "JPN";
            dr["PREF_CODE"] = "AIC";
            dt.Rows.Add(dr);

            var dr2 = dt.NewRow();

            dr2["ORDER_NO"] = "0002";
            dr2["USER_NO"] = "BBBB";
            dr2["COUNTRY_CODE"] = "USA";
            dr2["PREF_CODE"] = "LA";
            dt.Rows.Add(dr2);

            var dr3 = dt.NewRow();

            dr3["ORDER_NO"] = "1003";
            dr3["USER_NO"] = "CCCC";
            dr3["COUNTRY_CODE"] = "UK";
            dr3["PREF_CODE"] = "RD";
            dt.Rows.Add(dr3);

            var dr4 = dt.NewRow();

            dr4["ORDER_NO"] = "1004";
            dr4["USER_NO"] = "DDDD";
            dr4["COUNTRY_CODE"] = "FR";
            dr4["PREF_CODE"] = "Z";
            dt.Rows.Add(dr4);

            // ここから、DataTable→List<DataRow>への変換処理。

            // 単にList<DataRow>に変換する場合、ToList<DataRow>()を使用する。
            var list1 = dt.AsEnumerable().ToList<DataRow>();

            // AsEnumerable()を実行すれば、Listに変換する前に
            // Linqを使用することももちろん可能。

            // Where()でフィルタをかけたり...
            var list2 = dt.AsEnumerable()
                .Where(x => x["ORDER_NO"].ToString().Substring(0, 1) == "1")
                .ToList<DataRow>();

            // DataTableの各行のデータを編集して、Listにする事も可能。
            Func<DataRow, string> getRight = x =>  
             (x["PREF_CODE"].ToString().PadRight(2).Length <= 2) ?  
              x["PREF_CODE"].ToString().PadRight(2) : x["PREF_CODE"]  
              .ToString().Substring(1 ,2);

            var list3 = dt.AsEnumerable()
                .Select(x => x["ORDER_NO"].ToString() +  
                 x["COUNTRY_CODE"].ToString().PadRight(3) +  
                  getRight(x)).ToList<String>();
        }
    }
}

今回は少し短めですが、ちょっとC#について調べましたよ...ってことで。

Listに参照型の要素を追加する。(値型と参照型の違い)

「値型」と「参照型」

c#に限らず、プログラムをやると出てくる「値型」と「参照型」。

ややこしい部分であり、特に「参照型」はバグの原因になることも多いですので、実験結果も兼ねてメモ。

言い換えれば、こういうこと

僕が初学者の人に「値型と参照型の違い」について質問された時、下記の説明をします。

  • 値型(の代入):ファイルの「コピー&ペースト」
  • 参照型(の代入):ファイルの「ショートカットの作成」

前者の場合、元のファイルの内容をいくら変えようと、コピペ先のファイルの内容は変わりません。

でも後者の場合、作成元のファイルの内容を変えれば、ショートカットをダブルクリックして開いたファイルの内容も変わっています。

なので、上記の説明を借りると、

  • 値型:ファイルの「内容」(=値)をコピーする。
  • 参照型:ファイルの「リンク先」(=格納アドレス)をコピーする

と考えると、多少わかりやすいかも。

実際のコードで説明(クラスと構造体)

とはいえ「百聞は一見に如かず」なので、実験用のコードを下記に記載しました。 なおここでは、「似て非なるもの」としてよく例に出される、

  • クラス(=参照型)
  • 構造体(=値型)

を用いて説明します。
ちなみに、クラスと構造体の違いとしては、下記の通り。

処理 クラス 構造体
参照型 値型
フィールド宣言と同時に初期化 ×
引数無しコンストラクタの使用 ×
デストラクタの使用 ×
処理時間 遅い 速い

※「処理時間」に関しては、「大量のデータを処理した場合の速さの比較」なので、別に「クラスは一概に処理が遅い」わけではない。

参照:【C#】構造体の使い方

public partial class Form1 : Form
{
    struct PREF
    {
        public string pref1;
        public string pref2;
        public string pref3;
        public string pref4;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var prefs = new PREF();
        var list1 = new List<PREF>();
        var list2 = new List<List<PREF>>();

        prefs.pref1 = "愛知";
        prefs.pref2 = "岐阜";
        prefs.pref3 = "三重";
        prefs.pref4 = "静岡";

        list1.Add(prefs);
        list2.Add(list1);

        // 1

        prefs.pref1 = String.Empty;
        prefs.pref2 = String.Empty;
        prefs.pref3 = String.Empty;
        prefs.pref4 = String.Empty;

        // 2

        list1.Clear();

        // 3

        prefs.pref1 = "徳島";
        prefs.pref2 = "高知";
        prefs.pref3 = "愛媛";
        prefs.pref4 = "香川";

        list1.Add(prefs);

        // 4

        list2.Add(list1);

        // 5
    }
}

上記のコードの1~5の箇所で「list1」と[list2]のpref1~pref4の値をチェックすると、下記の違いがあります。

  • 1&2

    • list1
      • [0]
        • [pref1] = "愛知"
        • [pref2] = "岐阜"
        • [pref3] = "三重"
        • [pref4] = "静岡"
    • list2
      • [0]
        • [0]
          • [pref1] = "愛知"
          • [pref2] = "岐阜"
          • [pref3] = "三重"
          • [pref4] = "静岡"
  • 3

    • list1
      • 要素無し
    • list2
      • [0]
        • 要素無し
  • 4

    • list1
      • [0]
        • [pref1] = "徳島"
        • [pref2] = "高知"
        • [pref3] = "愛媛"
        • [pref4] = "香川"
    • list2
      • [0]
        • [0]
          • [pref1] = "徳島"
          • [pref2] = "高知"
          • [pref3] = "愛媛"
          • [pref4] = "香川"
  • 5

    • list1
      • [0]
        • [pref1] = "徳島"
        • [pref2] = "高知"
        • [pref3] = "愛媛"
        • [pref4] = "香川"
    • list2
      • [0]
        • [0]
          • [pref1] = "徳島"
          • [pref2] = "高知"
          • [pref3] = "愛媛"
          • [pref4] = "香川"
      • [1]
        • [0]
          • [pref1] = "徳島"
          • [pref2] = "高知"
          • [pref3] = "愛媛"
          • [pref4] = "香川"

動作の説明

  • 1:特に問題ないと思います。

  • 2:元の「prefs」の全フィールドの値を「String.Empty」にしていますが、prefsは(値型の)構造体なので、元のprefsが変更されても、list1の内容は変わりません。

  • 3:「list1.clear()」をしているので「list1」の要素はクリアされますが、list1は(参照型の)クラスなので、1の直前にlist1を追加した「list2」も、「list1」の変更の影響を受けます。 (ただし、list1自体が無くなったわけではないので、list1の参照自体は残っています。(=list2.Countの値は1のまま))

  • 4:ここが一番予想しにくい箇所。 「list1」に要素が変更されているのは当然ですが、「list2[0]」要素は「list1の参照先」なので、「list1」の要素が変更されれば、list2[0]の内容も直接影響を受けます。

  • 5:4でlist2[0]の要素が変更されましたが、list2.add()が実行されたので、list2[1]に新たにlist1の内容が追加されます。 結果、全く同じリスト(=list1)が2つ入っている、という結果になります。

と、参照型の代入は、想定外の動作をする場合があるので、気を付けないと、思わぬバグの原因になったりします。

回避策

簡単に言えば、「クラスの値のみコピーしたリストを追加する」処理を実施すればOKです。 やり方ですが、ソースのlist2.add(list1)の箇所を、

list2.add(new List<List<PREF>>(list1))

のように、

  • コンストラクタの引数に値コピー元のリストを設定して新規作成したたListクラス

を追加すれば、元のリスト(list1)の値が変更されようと、list2の内容は変わりません。

クラスと構造体の使い分け

最後になりますが、たまに参考書や一部のブログで、

迷ったら、クラスを使っておけば問題ない

という記載を見かけますが、何も考えずにクラスを使うと、上記のような思わぬバグを引き起こすので、要注意です。

個人的には、

  • クラス:色々な機能を付ける、拡張性を持たせる
  • 構造体:単に「値の受け皿」として使用する

として使い分けするのが良いかと思います。

以上、今回は長文になってしまいました。

Microsoftの「Sketch2Code」サービスを使ってみた感想

先日、Microsoftが「手書きの画面をHTMLに変換する」サービス「Sketch2Code」というサービスを発表しました。

www.atmarkit.co.jp


主にバックエンドがメインで、HTMLやCSSに触れる事が少ない僕としては、

  • Web系言語の勉強の際、UI(HTML/CSS)の作成に割く時間を減らせる
  • 業務の際、資料に書いた画面イメージからHTMLを作成できれば、かなりの時間を削減できる

のはかなりメリットだなと思ったので、期待を込めて、さっそく試してみました。


実用はまだ難しい...

で、試した結果が下記の画像。(簡単なフォーム画面)
左から順に

  • 作成した画面イメージ(Excelで作成)
  • 認識された構成
  • 自動生成されたHTML(の結果)

になります。

f:id:Makky12:20180909123601p:plain

と、結果としては、ちょっと残念な結果でした。
(手書きじゃないのがまずかったのかな?あるいは日本語認識が
まだ発展途上?)*1

先述のケースでHTMLの作成時間を減らせるのは、かなりメリットなのですが...*2
少なくとも、日本語をちゃんと認識できないと、業務で使用するのは難しいでしょうね。


これからに期待

とまあ、ちょっと実用は難しかったのですが、これから期待大なサービスであることは間違いないです。
Azureに力をかなり入れているマイクロソフトが開発しているのですから、品質もこれから改善されていくと思います。

それこそ先述のケース(特に業務)でHTMLの作成時間を軽減できれば、エンジニアの負担も減るのは間違いないですからね。*3


AIに仕事を取られる?

ただ、AIが色々出来るようになると、必ず話題になるのがこれ。

今はまだ問題ないでしょうが、今後技術がどんどん進歩すれば、「コーディングは全部AIにおまかせ」みたいになっていくんでしょうかね。

ただ個人的には、AIが進歩すれば、そのAIの品質向上のためにエンジニアが頑張る必要があるので、無くなる需要がある一方、下記のような、また別の需要が生まれると思います。

  • AIや機械学習分野に特化したプロフェッショナル
  • AIに関するロジック・業務分析など、コーディング以外の分野に特化したエンジニア


結局、求められるのは同じ
少なくとも(今でも言われている話ですが)、

  • 古い技術に対するこだわり
  • (古い体質・技術の)会社依存

を捨てられるか、そして

  • 時代にあった技術を習得・活用できるか

がエンジニアが求められるし、それは今も同じなんでしょうね。

*1:うまくHTMLを作成出来る画像のコツを知っていたら、どなたか教えて下さい...

*2:ただ、減ったら減ったでまた新しいタスクを入れるんでしょうね。これだから日本企業は...

*3:Excelの自動生成マクロからも解放される...のかな?

Githubにファイルをpushする際のトラブルシューティング

先日、フリーランス向けの「ふるさと納税可能額算出ツール」という
ツールをGithubに公開しました。

github.com

ただ、その際に色々ハマったことがありましたので、
備忘録も兼ねて対策方法を記載しておきます。


■SourceTreeが重いときの対処方法
Git操作系のGUIツールで有名な「SourceTree」ですが、このSourceTreeが
異常に重いことがあります。

SourceTree:https://ja.atlassian.com/software/sourcetree

「内蔵GitとシステムGitのバージョン差異」に起因する何かが
原因らしいですが、調べたところ、下記のような対処方法があるようです。

1. 一部ドライバを削除する

AMD Radeon graphics driver」というドライバを削除したり、
更新したら改善した、というケースがあるようです。
※もちろん、これやったから100%直る訳ではない。

Windows10 64bit でgitBashやSourceTreeがやたら遅い 重い


2. SourceTreeで「システムGitを使う」設定に変更する

SourceTreeの設定で「内蔵Gitを使う」設定になっている場合、
重くなるケースがあるようです。

その場合、
1. Gitの最新バージョンをインストールする。
2. SourceTreeの設定を「システムGitを使う」

とすると、改善する場合があるようです。(僕は直りませんでしたが...)

3. 諦めて、CUIベースで操作する

正直な話、これが一番確実な方法では?と思います。
Gitのコマンド自体はそんなに多いわけではないし、リポジトリ
クローンした後の基本的な、

  • ステージング
  • ローカルコミット
  • リモートpush

という作業だけなら、テンプレートのスクリプトファイルを作って、適宜
必要な箇所だけ修正し、そのスクリプトを実行すれば、毎回コマンドを入力する
手間も省けますし。

何より、無理にSourceTreeの調査に時間を充てるくらいなら、その方が
よっぽど時間を有効に使えるのでは?というのが最大の理由です。


■.gitignoreファイルが作成できない

.gitignore(管理しないファイルの設定ファイル)ファイルですが、普通に
Windowsエクスプローラ上で新規作成しようとしても、作成できません。
(「ファイル名を入力してください」とエラーになる。)
※「gitignore」を拡張子として認識してしまうため。


これを回避するには、

1. ファイル名を「.gitignore.」と入力して、新規ファイルを作成する。
(最後に「.」を付ける)
2. 「拡張子が付いていない...」というメッセージが表示されるので、
「はい」をクリックする。

これでエクスプローラ上でも「.gitignore」ファイルが作成できます。

というか、ファイル作成はGitHub上で行うのが無難かも。


■*.mdファイルのMarkdown記法がリモートリポジトリ上で効いてない場合の対処方法

ReadMeなどの*.mdファイルを更新した際、何故か途中から「Markdown記法が
全く反映されない」という現象が発生しました。

原因は、おそらく途中で設定ファイルの「改行コード」の設定を
手動で変えたことに起因する何か(内部コードがおかしくなった、等)だと
思われます。(ただ、Visual Studio CodeMarkdownビューアーでは、
全く問題なかったんですよね…)

これ自体の解決としては、単純に

  • Markdownが反映されない部分以前の空白文字を削除する
    (場合によっては書いた内容も)

で解決できますが、文字コードの設定は結構重要な部分なので、まとめました。

  • Github上で表示するファイル(*.mdファイルなど)は、改行コードは
    「LF」にする。
  • Gitのインストール時の改行コード設定に注意


f:id:Makky12:20180818073602p:plain

たいていインストールの設定って、デフォルトのまま「次へ」を連打して
しまいがちですが、(Gitは英語表記なのでなおさら)、
Gitの「改行設定」だけは注意が必要です。

上記画像では、上から順に

  • チェックアウト時はLF→CRLFに変換/push時はCRLF→LFに変換
  • チェックアウト時は変換なし/push時はCRLF→LFに変換
  • チェックアウト時/push時ともに変換なし

なんですが、真ん中の「チェックアウト時は変換なし/push時は
CRLF→LFに変換」にしておくのが、無難のようです。

気をつけて!Git for Windowsにおける改行コードqiita.com

※一番下の「変換無し」設定は、上記画像でもわかる通り、クロス
 プラットフォームの環境では非推奨なので、選ばないほうが無難かも。
※「変換無し」設定の場合、ツール側で設定などを行う必要があります。
 VS Codeなら「検索/置換」ウィンドウの「正規表現を使う」オプションを
 ONにすれば、改行コードでの検索/置換が可能です。
※ただ「変換あり」「変換無し」それぞれにメリット、デメリットがあり、
 一概にはどちらが良い、とも言えないようです。
qiita.com

以上、僕がGitHubにファイルをpushする際に遭遇したトラブル、および
その解決策でした。


てか、また前回からかなり間が開いてしまった...

業務都合だったり、あとメンタルの調子を崩したり、色々あったから
なのですが、やはりもう少し更新頻度を上げないと。
別に書きたいネタはそれなりにあるので、別にプログラミングのネタに
こだわらなくてもいいから、もう少し更新頻度を上げないといけませんね。
(こればっか)