20200603のC#に関する記事は8件です。

ViewModel と変更通知プロパティ

はじめに

オレオレ解釈の覚え書き その9

一番最初にやるべきだった変更通知プロパティについてまとめます。
タイトルには「ViewModel と~」とありますが、Model 層で使用される場合もあります。

本文

MVVM では TextBox.Text や CheckBox.IsChecked 等の依存関係プロパティに、ViewModel で定義された変更通知プロパティをバインドさせることで値を伝播することができます。つまり View の変更を ViewModel へ、ViewModel の変更を View へと、同期を取り合うということです。

今回は ViewModel に定義する変更通知プロパティについて紹介します。これは通常のプロパティ(いわゆる自動実装プロパティ)とは異なり、自身の値の変更を通知する機能を併せ持つプロパティを指します。原始的には INotifyPropertyChanged インターフェースを実装したクラス(これが ViewModel となります)を用意し、プロパティのセッターではバッキングフィールドを確認、値に変更があれば置き換えたうえで INotifyPropertyChanged.PropertyChanged イベントを発生させます。

ViewModel
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace TestApp.ViewModels
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
            => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private string _text;
        public string Text
        {
            get => this._text;
            set
            {
                if (this._text != value)
                {
                    this._text = value;
                    this.OnPropertyChanged();
                }
            }
        }
    }
}

愚直に実装すると上記のようになりますが、プロパティの部分はともかくとして、変更通知のイベントを自前で処理することは少ないはずです。大半の MVVM フレームワークではこれらの機能を搭載した基底クラスが提供されており、Prism の BindableBase、Livet の NotificationObject、MVVM Light Toolkit の ObservableObject などがそれにあたります。
先の例を Prism を使用して書き換えると次のようになりスッキリします。

ViewModel
using Prism.Mvvm;

namespace TestApp.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _text;
        public string Text
        {
            get => this._text;
            set => this.SetProperty(ref this._text, value);
        }
    }
}

なお View はコントロールのプロパティにバインドさせるだけです。Mode で同期の方向を、UpdateSourceTrigger でタイミングを制御することもできます。この辺りの仕様は .NET のリファレンスを確認してください。

View
<Window x:Class="TestApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:p="http://prismlibrary.com/"
        p:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</Window>

おわりに

今回は View と ViewModel をつなぐ仕組みの中でも、頻出で重要な機構である変更通知プロパティについてでした。
移譲コマンドは処理の移譲だけでしたが、この変更通知プロパティがあることで値を受け渡すことも可能になります。ここまで役者が揃ってくると、多くの処理を MVVM パターン上で構築できることに気づきます。とはいえ、構築できることと楽に実装できることの間には溝があり、正直今回の例にあるようにプロパティのセッターで一々変更を通知するのは面倒です。複数のプロパティの変更通知を絡める処理などが出てくると管理も大変になるでしょう。

次回は、このような変更通知やリアクティブプログラミングを強力にサポートするライブラリである ReactiveProperty について、表面だけサラッとご紹介します。

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

ジェネリック特殊化ノウハウまとめ

最近お世話になる機会が増えてきたので忘れないようまとめておく。
以前自分で検証した内容も踏まえて。

手法選択フローチャート

flowchart.png

サンプルコード

sample.cs
using System;
using System.Runtime.CompilerServices;

public static class Program
{
    public static void Main(string[] args)
    {
        DoSomething(123);
        DoSomething(0.123);
        DoSomething(new Bar());
        DoSomething("hogehoge");
    }

    public static T DoSomething<T>(T value)
    {
        // 1. typeof & Unsafe.As
        if(typeof(T) == typeof(int))
        {
            var value_int = Unsafe.As<T, int>(ref value);
            var return_int = DoSomething_int(value_int);
            return Unsafe.As<int, T>(ref return_int);
        }

        // 2. is operator
        if(value is Foo value_Foo)
        {
            return DoSomething_Foo(value_Foo) is T return_Foo
                   ? return_Foo
                   : throw new InvalidOperationException();
        }

        // 3. Generic type cached strategy
        return DoSomethingStrategy<T>.Instance.Invoke(value);
    }

    public static int DoSomething_int(int value)
    {
        Console.WriteLine($"{value} -> specialized on int");
        return value;
    }

    public static Foo DoSomething_Foo(Foo value)
    {
        Console.WriteLine($"{value} -> specialized on Foo");
        return value;
    }


    private class DoSomethingStrategy<T>
    {
        // 3'. strategy pattern & Activator
        public static DoSomethingStrategy<T> Instance { get; }
            = typeof(T).IsValueType
              ? (DoSomethingStrategy<T>)Activator
                    .CreateInstance(typeof(DoSomethingStrategy_struct<>)
                    .MakeGenericType(typeof(T)))
              : new DoSomethingStrategy<T>();

        public virtual T Invoke(T value)
        {
            Console.WriteLine($"{value} -> default");
            return value;
        }
    }

    private class DoSomethingStrategy_struct<T> : DoSomethingStrategy<T>
        where T : struct
    {
        public override T Invoke(T value)
        {
            Console.WriteLine($"{value} -> specialized on struct");
            return value;
        }
    }
}

public class Foo {}
public class Bar : Foo {}

1., 2., 3. の手法は1つのジェネリックメソッド内で共存可能だが、サンプルのように順序を守ること。

解説

1. typeof & Unsafe

if(typeof(T) == typeof(int))
{
    var value_int = Unsafe.As<T, int>(ref value);
    var return_int = DoSomething_int(value_int);
    return Unsafe.As<int, T>(ref return_int);
}

Tのtypeofを取って特殊化先の型と等値比較し、分岐の際には型変換にUnsafe.Asを用いる。
特殊化先が値型であれば、JIT後にはなんとゼロオーバーヘッドという素敵性能。

参考:最速のジェネリック特殊化を目指して

2. is演算子

if(value is Foo value_Foo)
{
    return DoSomething_Foo(value_Foo) is T return_Foo
           ? return_Foo
           : throw new InvalidOperationException();
}

C#における最も標準的なダウンキャストを用いる手法。
派生型もまとめてディスパッチできるが、nullをスルーしてしまうので非null限定であることに注意。

3. generic type cached strategy

return DoSomethingStrategy<T>.Instance.Invoke(value);
private class DoSomethingStrategy<T>
{
    // 外部からデフォルトインスタンスを流し込む場合にはsetアクセサも公開する必要がある
    public static DoSomethingStrategy<T> Instance { get; set; }
        = new DoSomethingStrategy<T>();

    public virtual T Invoke(T value)
    {
        Console.WriteLine($"{value} -> default");
        return value;
    }
}

Generic type cachingによりストックしたStrategyを用いる手法。
特殊化型ごとに独立したクラスを定義できるため特殊化実装間の疎結合を保ちやすい。
特殊化先の型ごとにデフォルトインスタンスの初期化をする必要があることに注意。

3'. strategy + Activator

private class DoSomethingStrategy<T>
{
    // Activatorによるインスタンス生成をすることで型制約を回避する
    public static DoSomethingStrategy<T> Instance { get; }
        = typeof(T).IsValueType
          ? (DoSomethingStrategy<T>)Activator
                .CreateInstance(typeof(DoSomethingStrategy_struct<>)
                .MakeGenericType(typeof(T)))
          : new DoSomethingStrategy<T>();

    public virtual T Invoke(T value)
    {
        Console.WriteLine($"{value} -> default");
        return value;
    }
}

private class DoSomethingStrategy_struct<T> : DoSomethingStrategy<T>
    where T : struct
{
    public override T Invoke(T value)
    {
        Console.WriteLine($"{value} -> specialized on struct");
        return value;
    }
}

stragetyのデフォルトインスタンスを生成するときに、Activatorを介することで型制約を回避するテクニック。
C#では型制約の弱いメソッドから強いメソッドを呼ぶことが(アンセーフな手法でも)できないので、その代替手段として利用できる。
class制約、struct制約、new制約、複数のインターフェース制約など、どんなものにも対応できる。

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

Unityでマウスの動きに合わせてGameObjectを動かす方法

はじめに

こんな感じのものを作ります

movie1.gif

ステップ

  1. 動かしたいGameObjectを作成します
  2. 「MouseChase.cs」の名前でC#スクリプトを生成します
  3. 2のスクリプトを1のGameObjectにアタッチします

スクリプト

MouseChase.cs
using UnityEngine;

public class MouseChase : MonoBehaviour
{
    /// <summary>
    /// Update
    /// </summary>
    void Update()
    {
        transform.position = Input.mousePosition;
    }
}

補足

transform.position = Input.mousePosition;

上記の箇所ですが、以下のように書くこともできます

gameObject.transform.position = Input.mousePosition;

違いは参考のURLにて詳しく書いてますので、参考にしてみてください
上の記述のほうが早いようです

参考

gameObject.GetComponent() と transform の違い(または Unity における省略記法について)

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

【C#】継承できるクラスでDisposeパターンを実装するときは、とりあえずFinalizeも実装すべき?

はじめに

IDisposableなんて別にpublic void Dispose()を実装するだけでも事足りることばかり。でもどうせならDisposeパターンなんてより丁寧に書くための物があるんだから使いたくなるのが開発者の心情というもの。と言うわけでIDisposableを使うときの個人的なメモ。
「より安全で丁寧なコードを書くにはどうしたら良いだろう?」という考え方が、以前書いた「【Windows/C#】なるべく丁寧にDllImportを使う」に通じるものがあるかも・・・1

Disposeパターン

C#でよくリソースを破棄するために使うIDisposableインターフェイス。
それを丁寧に扱うためのテンプレートとして有名なDisposeパターン
Visual Studio 2019だとclass A : IDisposableと打ち込んで、右クリックメニューから「クイックアクションとリファクタリング...」から「破棄パターン2を使ってインターフェイスを実装します」を押すと自動的に生成してくれるアレ。

class A : IDisposable
{
    private bool disposedValue;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: マネージド状態を破棄します (マネージド オブジェクト)
            }

            // TODO: アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします
            // TODO: 大きなフィールドを null に設定します
            disposedValue = true;
        }
    }

    // // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
    // ~A()
    // {
    //     // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
    //     Dispose(disposing: false);
    // }

    public void Dispose()
    {
        // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

ファイナライザ いる?いらない?

自動生成されるコードでは、ファイナライザがコメントアウトされていて「アンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします」と書いてある。
またDisposeパターンの正しい実装を求めてグーグル先生に尋ねてみたけど、どの情報も「アンマネージドリソースを使う場合はファイナライザを実装する」みたいな書き方になってた。
本当にそれで安全なのかな?

継承先でアンマネージドリソースが使われると...?

protected virtual void Dispose(bool disposing)

virtualを付けているくらいだから継承できるはず。
とすると、そのクラスでアンマネージドリソースを使わなかったとしても、継承先で使われる可能性は十分あり得るのでは・・・?
もしその時に継承元がファイナライザを実装していないとどうなるのかな?

継承元のファイナライザが無いとどうなる?

まずファイナライザを実装した場合を実験

継承元のAクラス。

class A : IDisposable
{
    protected virtual void Dispose(bool disposing)
    {
        Console.WriteLine($"A.Dispose({disposing})");
    }

    // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
    ~A()
    {
        Console.WriteLine($"A.~A()");

        // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
        Dispose(disposing: false);
    }

    public void Dispose()
    {
        Console.WriteLine($"A.Dispose()");

        // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

継承先のBクラス。

class B : A
{
    protected override void Dispose(bool disposing)
    {
        Console.WriteLine($"B.Dispose({disposing})");

        base.Dispose(disposing);
    }
}

あと実験用のProgram.cs。

class Program
{
    static void Test()
    {
        B b = new B();
    }

    static void Main(string[] args)
    {
        Test();
        GC.Collect();
    }
}

単純にDisposeとファイナライザを呼び出したときにコンソール出力するだけのクラス。
それをわざとDisposeをすっぽかしてみる。

結果

A.~A()
B.Dispose(False)
A.Dispose(False)

当然BクラスのDisposeFalseで呼ばれている。
ちなみにGC.Collect()を呼ばないと、ファイナライザを実装してても呼ばれなかった。

次にファイナライザを実装しなかった場合を実験

アンマネージドリソースを使ってないからとAクラスのファイナライザは実装しない。

class A : IDisposable
{
    protected virtual void Dispose(bool disposing)
    {
        Console.WriteLine($"A.Dispose({disposing})");
    }

    // // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
    // ~A()
    // {
    //     // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
    //     Dispose(disposing: false);
    // }

    public void Dispose()
    {
        Console.WriteLine($"A.Dispose()");

        // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

継承先のBクラスでは、ファイナライザでDispose(false)を呼んでくれると信じて、アンマネージドリソースの破棄処理を実装したとする。

class B : A
{
    protected override void Dispose(bool disposing)
    {
        Console.WriteLine($"B.Dispose({disposing})");

        if (disposing)
        {
            Console.WriteLine($"マネージド リソースを破棄!");
        }

        Console.WriteLine($"アンマネージド リソースを破棄したい!");

        base.Dispose(disposing);
    }
}

そしてさっきと同じように実行。

結果

しーん・・・
やはり実行されなかった。

どうすべきか?

少なくともsealedが付いていないクラス3IDisposableを実装するときは、
「継承してもアンマネージドリソースを使うわけがないクラス」
「そもそも継承する予定のないクラス」
「Disposeは必ず呼ぶから大丈夫」
色々あるだろうけど、別にファイナライザを実装しても行数が増える程度で特に問題はないので、なにも考えずにファイナライザのコメントを解除して実装してしまうのが1番安全な気がする。
作成当時は「有り得ない」と思ってても、時間が経ってから継承してアンマネージドリソースを使うかもしれない。

もちろん「アンマネージドリソースを使った側でファイナライザを実装する」というのもアリかもしれない。

class B : A
{
    ~B()
    {
        Dispose(disposing: false);
    }

    protected override void Dispose(bool disposing)
    {
        Console.WriteLine($"B.Dispose({disposing})");

        if (disposing)
        {
            Console.WriteLine($"マネージド リソースを破棄!");
        }

        Console.WriteLine($"アンマネージド リソースを破棄!");

        base.Dispose(disposing);
    }
}

むしろ「アンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします」という説明的には、こっちの方が正しい気さえする。
でも継承先でDisposeをオーバライドしてもIDisposableのテンプレートみたいにファイナライザは自動生成されないし、絶っっ対にファイナライザの実装を忘れると思う。
なによりいちいち継承先で実装するのは面倒臭い。

というわけで、IDisposableDisposeパターンで実装するときは、自動生成のコメントに惑わされず常にファイナライザは実装していこうかなーって話でした。

余談

自動生成されるDisposeパターンのコードがいつの間にか微妙に変わってる?

public void Dispose()
{
    // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
    Dispose(disposing: true);
    GC.SuppressFinalize(this);
}

なんで引数にdisposing:をわざわざ付けるんだろう・・・
以前はGC.SuppressFinalize(this);もコメントアウトされてて、アンマネージドリソースを使う場合のみ呼び出す形だったような・・・


  1. 書き出しを合わせてみたけど、シリーズ化する予定はない模様 

  2. 破棄パターン(笑)昔は「Disposeパターン」と表記されてたのに機械翻訳でこうなってしまったのかな・・・ 

  3. 別にsealedとか気にせずファイナライザを実装して良い気もする 

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

備忘録

備忘録。

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

C# 連番の数値をグループわけする方法

概要

連番の数値をグループ化する際に嵌ったので自分用のメモとして残しておく。

以下のような配列形式の数値を連番を整形して
int [] number = { 1,2,3,6,9,10,15,16 }

以下のような形式で取得をしたい
1,2,3
6
9,10
15,16

実現方法

以下の方法で実現が可能です。

var number = { 1,2,3,6,9,10,15,16 };

// 配列の要素からインデックスの値を引いた値は同じになることを利用してグルーピングしています
var result = number.Select((x , index) => (x , index))
                   .GroupBy(n => n.x - n.index, n => n.x);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#からPythonスプリクトの標準出力を取得する

はじめに

C#からPythonスクリプトを実行し、標準出力を1行ずつ取得することを試してみました。

C#側の実装

Program.cs
class Program
{
    static void Main(string[] args)
    {
        //Pythonのパス
        var pythonInterpreterPath = @"/usr/local/bin/python3";
        //スプリクトファイルの場所
        var pythonScriptPath = @"Sample.py"

        System.Diagnostics.Process process = new System.Diagnostics.Process();

        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        //イベントハンドラーの追加
        process.OutputDataReceived += p_OutputDataReceived;

        process.StartInfo.FileName = pythonInterpreterPath;
        process.StartInfo.Arguments = pythonScriptPath;

        process.Start();
        //非同期の読み取り開始
        process.BeginOutputReadLine();

        process.WaitForExit();
        process.Close();
    }

    static void p_OutputDataReceived(object sender,
           System.Diagnostics.DataReceivedEventArgs e)
    {
        //出力された文字列を表示する
        Console.WriteLine(e.Data);
    }
}

参照:https://docs.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process.outputdatareceived?view=netcore-3.1

Python側の実装

Sample.py
import time

for i in range(9):
    time.sleep(10) #重い処理
    print(str(i+1)+'0秒待った')


出力が上手く行かない場合

print関数で出力がバッファ化されると、出力が最後にまとめて出てきます。その際はprint関数のflushオプションを用いることで解決出来ます。

sample.py
import time

for i in range(9):
    time.sleep(10) #重い処理
    print(str(i+1)+'0秒待った',flush=True)

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

Reactの人気を超えたASP.NET Coreとは?

2020 Web Developer Survey

StackOverflowの2020 Web Developer Surveyの「最も愛されるWebフレームワーク」分野で、ASP.NETがReactを超えて1位になりました。なんとなく聞いたことはあるのですが、実際にどのようなものかを調べてみました。

image.png

ASP.NETを普段から使ってるわけではないので、間違った内容があればご訂正して頂ければと思います。

Fullstack Web Framework

ASP.NETは、Microsoftが開発した開発者プラットフォームで、C#、F#、Visual Basicを利用して様々なアプリケーションを開発できるようにしたものです。

iOS・AndroidアプリをXamarinで開発でき、IoT・デスクトップ・機械学習なども作れます。
その中でもASP.NET Coreは、Webのフロントエンド・バックエンド開発が可能です。

使用している会社

ASP.NET Coreは、テンセントやStackOverflow、GoDaddyなど、巨大なWebサイトで使用されてます。
理由としては、エンタープライズが使いやすい仕様であることと、パフォーマンスがとても早いからです。

image.png

Node.jsやJavaのバックエンドが一秒に80万回程度のリクエストを処理できる反面、.NETでは約700万(7.33 million)リクエストを処理できます。なので、高いパフォーマンスが求められるサービスに適していると思われます。

Blazor

Reactを超えた人気を誇るようになった理由としては、Blazorが挙げられると思われます。
Blazorを使えば、フロントエンドをC#でビルドできます。

<h1>counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
  private int currentCount = 0;
  private void IncrementCount() 
  {
    currentCount++;
  }
}

Node.jsが人気になった理由同様、バックエンドもフロントエンドもC#で開発できるのは大きいメリットです。コードはC#で書き、コンパイルはWeb Assemblyを利用、必要なJSのAPIやライブラリーはJavaScript Interopで呼び出せます。

また、Blazorを利用すれば、クライアントロジックをサーバーで処理することもできます。つまり、クライアントサイドとサーバーサイドのインタラクティブさのみならず、SSRレベルのセキュリティも持てるようになります。

JavaScriptのエコシステムとの差

ASP.NET CoreはJS同様、同じ言語でフルスタックな開発ができるのにメリットがありますが、JSのエコシステムとの決定的な差は、JSは開発者の力量に頼りがちという点です。

JSの場合、開発環境の構築のために様々なオープンソースライブラリーをダウンロードし、自分なりの設定をしていかなければなりません。ASP.NET CoreはMSが多くのリソースを投資して誕生した「ビジネスプロダクト」なので、完成度が高く、すべてがフレームワーク内に用意されています。

チュートリアル

最後に

いかがだったでしょうか。普段からC#を触ってらっしゃる方々には嬉しい調査結果かもしれません。
ちなみに2019年の同じ調査では、ASP.NETは5位を記録していたので、早いスピードで人気を獲得しているのがわかりますね。
image.png

それにしても、最近コードエディターはVS Code、コードベースはGithub、フレームワークはASP.NETと、Microsoftの進撃が止まらないですね…

追記(20/06/04)
@unsignedint さんから補足頂いたので、本文に追記させてもらいました。

表題では合っていますが、文中のASP.NETはASP.NET Coreですね。この両者、かなり別物ですので……。グラフにも出ていますが、ASP.NETの方は環境を選ぶこともあるのか、13位にとどまっています。ASP.NET Coreはプラットフォームの自由度が非常に高くなったので一定のブレイクが見られたということかと思います。

この結果は非常に面白いと思います。というのも、Blazorは比較的新しい技術(サーバー版は去年の後半、WebAssemblyは今年の5月にGM)であり、中にはプレビュー版を試用していた回答者もいるかと思いますが、Blazorがどの程度の影響を与えたのかは興味深いところです。(ASP.NET Core自体は、Blazorを使わなくてもウェブアプリが開発できますので……現時点ではまだそちらが主流かと思います。)

また、ASP.NET Coreの人気度が高い一方、Wantedの方ではReactが依然と高くなっているため、使用者からみてASP.NET Coreの満足度が高い一方、他のフレームワークの使用者から引き寄せる力がまだまだ低い、ということなのではないかと解釈しています。

先月、Blazorサーバー・WebAssemblyが出揃い、また、今後のリリースでパフォーマンスの向上等が予定されていることを考えると、今後、更に飛躍する可能性が高そうですし、また、Wantedの方も伸びていくかもしれません。(伸びればいいな、と思っています。Blazor非常に快適なので……。)

@unsignedint

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