echo("備忘録");

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

【Office】Office365 Businessを申し込む方法や注意点

経緯

  • 今までOffce365 &Office Personal Premiun&永続版Excel&Word(2013)で使用していたが、所持PCが多くなり、賄えなくなってきた。
  • Office365 Soloを検討したが、Office365 Businessのほうがメリットが多そうだった。(値段・機能など)
  • Office365 Businessは、個人事業主でもOKみたいだった。

上記のこともあり、今回の更新から、Office365 Businessに変えることにした。

公式サイト:Microsoft Office 全製品の比較 | Microsoft Office

SoloとBusinessの違い

細かいところは色々ありますが、大きなところでは、下記あたりでしょうか。

項目 Solo Business
値段 12,744円/年 11,664円/年
Skype ×
OneDrive OneDrive OneDrive For Business
インストール可能台数 無制限 PC×5、スマホ×5、タブレット×5
同時使用可能台数 5台まで(機種問わず) 制限なし
個人アカウントとの連携 ×
  • 公式サイトを見るとBusinessは「900円/月(10800円/年)」に見えるが、あれば「税抜」価格(Soloは「税込」価格)。税込にすると上記の通り、90円/月(1080円/年)しか違わないので、値段は決定的な違いにはならない(と思う)。
  • むしろ他の要素の方が重要。(例えば、Businessは個人アカウントとの連携不可なので、OneDriveのデータは自分で移行しなければならない、ということ。)
  • 最大の違いは「インストール可能台数」と「同時使用可能台数」。2018/10/2よりSoloは「インストール台数は無制限だが、同時使用は機種を問わず5台まで」になった。
  • 一方Businessは「インストール台数は有限だが、同時使用台数は無制限」となっている。(てか公式サイトの記載、あれじゃ全然分からない...)

単純に「値段が安いから」で選ぶと、かえって面倒なことになったりするので、よく考えて決めましょう。

登録方法

下記のページを参照に、登録を行った。

注意点としては、下記の所でしょうか。 (またスクショなくて、申し訳ないです...)

■手順1(本人情報入力)

  • 「電子メール」は、個人アカウントのものと同一ではダメかも(「事業用」のものが必要?ちゃんと分けるのが無難だと思う。)
  • そのほかの情報(住所、電話番号など)は個人アカウントと同じで問題なかった。(携帯番号でもOK)

■手順2(ユーザーID作成)

  • ユーザーIDは、「【個人ID】@【会社ID】.onmicrosoft.com」 で固定(例えば、makky12@hatenablog.onmicrosoft.comなど)。
  • 【個人ID】:メアドの@の前の部分、【会社ID】:メアドのドメイン部分、と考えればいいかも。
  • 右上に「既存のサブスクリプションに追加しますか?」と表示されているが、個人のアカウントには紐づけ不可なので注意(法人用の既存アカウントのみ)。

■手順3(注文の確認)&手順4(注文)

  • 人数を聞かれるが、個人利用なら「1人」で全く問題ない。
  • マイクロソフトのOffice365 Businessページだと月払いしかできない感じだが、年払いも選択可能。
  • (参考ページにも書いてあるけど)ここでOffice365 Businessページの値段が「税抜」価格であるとわかる。(そう書いといてよ...と思う、確かに)

登録完了後

あとは、先述の「【個人ID】@【会社ID】.onmicrosoft.com」で専用ページにログイン後、インストーラをダウンロードして、各種OfficeソフトをインストールすればOK。(インストール可能なソフトはSolo/Businessで違いはない。)

Businessの場合の作業

  • 個人アカウントと連携不可なので、OneDrive内のファイルは、期限終了前に自分でOneDrive For Businessに移行しないといけない。(移行ツールなどは存在しない)
  • (僕のケースのように)永続ライセンスのOfficeソフト等を使っている場合、(必要ないのに)変にアンインストールなどをしないほうが良いと思う。(やっぱり再インストール...と思った場合、ライセンス認証がクッソ面倒。なぜか「台数が上限に」とか「電話での認証不可」とか言われたりするし)

単に値段だけで決めるのは早計ですが、やっぱり使用可能台数の差は大きいです。よく考えて、自分に合ったライセンスを選びましょう。

でも使用可能台数が多いのは、やはり便利です。これからSurfaceなど、別PCを買う機会も増えるでしょうし

【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の自動生成マクロからも解放される...のかな?