20210809のC#に関する記事は5件です。

【新卒研修】初めて新規開発をして学んだこと

はじめに こんにちは。 4月に新卒でイーリバースドットコムに入社した高倉です。 この記事は、私が3ヶ月の研修期間を経て感じたことをアウトプットする目的で書いております。 研修前のレベル感 私の大学での専攻は機械工学で、授業では軽くプログラミングに触れる程度。 授業以外で個人的に興味をもってプログラミングを勉強し始めたのは大学3年生の秋くらいでした。 YouTubeのチュートリアル動画やUdemyの講座を観ながら、実際にコードを書いて学習をしていました。 入社前に触ったことがあった技術は、以下の通りです。 言語:JavaScript, Ruby, Python, Go, C#, HTML, CSS ライブラリ・フレームワーク:React, Ruby on Rails その他:SQL, git, Docker, AWS 業務内容 新規の業務系バッチ処理機能開発における、以下の工程を行いました。 詳細設計 機能実装 テスト(単体、結合) 使用技術 C#(.Net Core3.1) AWS(AWS Batch, Amazon Aurora, DynamoDB) 先輩からの技術的な学び パフォーマンスを意識する Linqでクエリを投げる箇所の実装で、クエリ発行回数を減らすように以下のようにレビューを受けました。 修正前 foreach (data in datas) { var query = _sampleDbContext.SampleDb.Add{new SampleType // 省略 } query.Context.SaveChanges() //ここでクエリが発行される } 修正後 var i = 0; foreach (data in datas) { var query = _sampleDbContext.SampleDb.Add{new SampleType // 省略 } if (i % 100 == 0) { query.Context.SaveChanges() //データ量が膨大な場合の対処 } i++; } query.Context.SaveChanges() //データ量が膨大な場合の対処 それほど多くないデータ量であれば修正前でも問題ないのですが、今回扱うのはかなりの量のデータだったので修正が必要でした。 個人で勉強用に開発をしていたときは動かすことしか考えておらず、このような細かいパフォーマンスの面まで考えることはありませんでした。 しかしこれからは扱うデータ量が膨大なため、余計な負荷をかけない実装を心がけたいと思いました。 常にテストのことを考えて実装する DBのカラムに日付を挿入する処理で、SystemのDateTimeを使用していたところ、外部クラスにDateTimeを返すメソッドとして定義し、それを使うようにレビューを受けました。 修正前 var query = _sampleDbContext.SampleDb.Add{new SampleType date = System.DateTime.Now() } 修正後 using SystemDate; //外部に定義した自作クラス var query = _sampleDbContext.SampleDb.Add{new sampleType date = SystemDate.GetSystemDate() //DateTime.Now()を返すだけのメソッド } 「正しい日時がDBにインサートされること」のテストを書く際に、修正前だとテスト実行の度にDateTime.Now()は異なる日時を返すのでテストが書けません。 修正後のコードであれば、GetSystemDate()をモック化して指定した日付を返す仕様にできるので、テストが書けるようになります。 恥ずかしながら、テストコードは就職して初めて書きました。 実装時からテストのことも考えていないと後で困るということを学んだので、次に活かしたいと思います。 使う人のことを考えてログメッセージを作る 修正前 _logger.LogError("APIからの距離データ取得に失敗しました。") 修正後 _logger.LogError("APIからの距離データ取得に失敗しました。、データNo={0}、始点住所={1}、終点住所={2}", dataNo, originAddress, distinationAddress); 修正前だとエラーは発見できてもどのデータで発生したのかわかりません。 ログの出し方に正解はありませんが、理想はそのメッセージを読んだ人が誰であっても、適切な対処を取れることだと思いました。 全体的な研修の感想 入社前に不安になる必要はなかった 新卒一期生かつエンジニアの新卒は私だけだったこともあり、研修や業務についていけるか不安でした。 そのため、入社前に会社で使っている技術を中心にぼちぼち勉強をしていました。 (コロナで卒業旅行などに行けなかったので、時間だけはありました(笑)) しかし、そこまで心配する必要はなかったなと思います。 どれだけ勉強していっても、結局先輩に聞きまくってググり倒していくことになるので安心してください(笑)。 むしろそれが当たり前で、入社してから質問する力を鍛えていく方が重要だと学びました。 とはいえ、会社で使っている技術の基礎だけでも抑えておくと、入社後に圧倒的に楽なのは間違いありません。 もし時間があれば、基礎だけでもやっておくことをオススメします。 実務的な研修のデメリット 私が行ったような実務的な研修は、体系立った学習をするだけの研修よりも実践的なスキルが身に付くというメリットがあると感じました。 対してデメリットは、先輩社員のサポートがあるとはいえ難易度の高いタスクが渡される時があることです。 初めのうちは「それが自分にできることなのか」という判断は難しいため「良く分からない所」や「製品の質に直結する」という実装などは、迷わず先輩に聞くことが大事だと思いました。 私の場合は、周囲の先輩のフォロー&レビューなどもあり、大きなミスは起こりませんでした。 毎日の充実感 研修といえど、自分が書くコードはお客様が将来実際に使う製品です。 それがなによりのモチベーションになりましたし、エンジニアとして働けているんだという充実感に大きく繋がりました。 学生のときのように自分で勉強するだけの日々では、絶対に味わえなかった感覚です。 最後に この記事が、新卒もしくは転職でエンジニアを目指している方の参考に少しでもなりましたら幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】ARFoundationのImageTrackingで複数の画像マーカーそれぞれに対応したARオブジェクトを出現させる

はじめに ARFoundationのImageTrackingを使えば登録した画像マーカーを認識することにより、 任意のARオブジェクトを出現させることができます。 【参考リンク】:ARFoundationを触ってみた 実は画像マーカーを複数登録することも可能なようです。 今回はそれについて調べたのでメモします。 内容としては"認識した画像マーカーそれぞれの上にARオブジェクトを出す"だけに留めます。 下記のような難しい実装の手法をこの記事で書くわけでないということ予め明示しておきます。 ×複数の画像マーカーの位置関係を固定した、広域対応のARの手法。 【参考リンク】:【GyroEye Holo】マーカー位置 デモ 画像マーカー二枚のそれぞれに対応したARオブジェクトを表示しています。 あまり用途は無いかもですが、ロスト時に非表示にする実装も試してみました。 バージョン情報 諸々名前 バージョン Unity 2020.3.4f1 ARFoundation 4.0.12 ARCore XR Plugin 4.0.12 ARCore Kit Plugin 4.0.12 XR Plugin Management 4.0.1 ※Andoroidでしか試していませんが、ARFoundationなのでiosでも動くはず、、、です。 下準備 バージョン情報に従って一通りビルドできるまでの環境構築が終わったら、下記画像を参考にReferenceImageLibraryを作成します。 次にReferenceImageLibraryを設定します。 あとはAR Session OriginにAR Tracked Image Managerを設定し、 Serialized Libraryに先ほど作成したReferenceImageLibraryを設定すれば準備完了です。 コード 適当なオブジェクトにアタッチ using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; /// <summary> /// 画像マーカー複数対応のサンプル /// </summary> public class MultiMarker : MonoBehaviour { /// <summary> /// マーカー用オブジェクトのプレハブ /// </summary> [SerializeField] private GameObject[] _arPrefabs; /// <summary> /// ARTrackedImageManager /// </summary> [SerializeField] private ARTrackedImageManager _imageManager; /// <summary> /// マーカー用オブジェクトのプレハブと文字列を紐づけた辞書 /// </summary> private readonly Dictionary<string, GameObject> _markerNameAndPrefabDictionary = new Dictionary<string, GameObject>(); private void Start() { _imageManager.trackedImagesChanged += OnTrackedImagesChanged; //辞書を作る 画像の名前とARオブジェクトのPrefabを紐づける for (var i = 0; i < _arPrefabs.Length; i++) { var arPrefab = Instantiate(_arPrefabs[i]); _markerNameAndPrefabDictionary.Add(_imageManager.referenceLibrary[i].name, arPrefab); arPrefab.SetActive(false); } } private void OnDisable() { _imageManager.trackedImagesChanged -= OnTrackedImagesChanged; } /// <summary> /// 認識した画像マーカーに応じて紐づいたARオブジェクトを表示 /// </summary> /// <param name="trackedImage">認識した画像マーカー</param> private void ActivateARObject(ARTrackedImage trackedImage) { //認識した画像マーカーの名前を使って辞書から任意のオブジェクトを引っ張り出す var arObject = _markerNameAndPrefabDictionary[trackedImage.referenceImage.name]; var imageMarkerTransform = trackedImage.transform; //位置合わせ var markerFrontRotation = imageMarkerTransform.rotation * Quaternion.Euler(90f, 0f, 0f); arObject.transform.SetPositionAndRotation(imageMarkerTransform.transform.position, markerFrontRotation); arObject.transform.SetParent(imageMarkerTransform); //トラッキングの状態に応じてARオブジェクトの表示を切り替え arObject.SetActive(trackedImage.trackingState == TrackingState.Tracking); } /// <summary> /// TrackedImagesChanged時の処理 /// </summary> /// <param name="eventArgs">検出イベントに関する引数</param> private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs) { foreach (var trackedImage in eventArgs.added) { ActivateARObject(trackedImage); } foreach (var trackedImage in eventArgs.updated) { ActivateARObject(trackedImage); } } } やっていることは下記です。 ①"ReferenceImageLibraryに登録した画像の名前"と"表示したいARオブジェクトのPrefab"を辞書で紐づける。 ②画像マーカー認識時に画像の名前に紐づけられたARオブジェクトを出現させる。 ARTrackedImageManager.trackedImagesChanged trackedImagesChangedに画像マーカー認識時のイベントハンドラーを設定できます。 イベントハンドラーの引数であるARTrackedImagesChangedEventArgsからは 下記3種類の情報をそれぞれのタイミングで得ることができます。 Type Name Description List added The list of ARTrackedImages added since the last event. List updated The list of ARTrackedImages updated since the last event. List removed The list of ARTrackedImages removed since the last event. 【引用元】:Struct ARTrackedImagesChangedEventArgs addedとupdatedの使い道は理解できたのですが、removedがいまいち使い道がわかりませんでした。 The list of ARTrackedImages removed since the last event.という説明から、画像をロストした時?かと思い、 その前提で実装を組んでみましたが、何をやっても呼ばれずです。 使い道等知ってる方いたら教えてください。 TrackingState 先ほど画像をロストした時について実装を試みたと書きましたが、 それについてはTrackingStateが有効でした。 ARTrackedImage(認識した画像)から三種類取得できます。 TrackingState Description Limited Some tracking information is available, but it is limited or of poor quality. None Not tracking. Tracking Tracking is working normally. 【引用元】: Enum TrackingState 簡単に言うとLimitedは認識精度が低い時、Noneは非認識時、Trackingは認識した時 といった感じです。 おわりに 画像マーカーを動かすと、再認識とロストを繰り返してARオブジェクトがカクカクし始めるので、 平面に固定することを前提として使った方がいいかも と色々と実験しながら思いました。 参考リンク AR Foundation Improved Image Tracking - Multiple Objects/Images - Unity Augmented Reality/AR
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ARFoundation】深度画像からポイントクラウドをリアルタイムに表示する【Unity】

概要 UnityのARFoundationのDepth画像とRGB画像を使用し、パーティクルでPointCloudの表示を実装する。 リポジトリ : https://github.com/AzetaTakuya/DepthToPointCloudForARFoundation ※本記事は実装の説明的な立ち位置。詳しくはリポジトリ見てもらった方が早そう。 環境 MacBook Air (M1, 2020) iPad Pro 11インチ(第2世代) Unity 2020.3.1f1 ARFoundation 4.1.0 実装 RGB画像を取得する ARCameraManagerのコールバックから、カメラ画像を取得する 参考:https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/cpu-camera-image.html [SerializeField] private ARCameraManager CameraManager; public Texture2D RGB_Texture { get; private set; } private void OnEnable() { CameraManager.frameReceived += OnARCameraFrameReceived; } private void OnDisable() { CameraManager.frameReceived -= OnARCameraFrameReceived; } unsafe void OnARCameraFrameReceived(ARCameraFrameEventArgs eventArgs) { if (!CameraManager.TryAcquireLatestCpuImage(out XRCpuImage image)) return; var conversionParams = new XRCpuImage.ConversionParams { inputRect = new RectInt(0, 0, image.width, image.height), outputDimensions = new Vector2Int(image.width / 2, image.height / 2), outputFormat = TextureFormat.RGBA32, transformation = XRCpuImage.Transformation.MirrorY }; int size = image.GetConvertedDataSize(conversionParams); var buffer = new NativeArray<byte>(size, Allocator.Temp); image.Convert(conversionParams, new IntPtr(buffer.GetUnsafePtr()), buffer.Length); image.Dispose(); if (RGB_Texture == null) { RGB_Texture = new Texture2D(conversionParams.outputDimensions.x, conversionParams.outputDimensions.y, conversionParams.outputFormat, false); } RGB_Texture.LoadRawTextureData(buffer); RGB_Texture.Apply(); buffer.Dispose(); } Depth画像を取得する AROcculusionManagerからenvironmentDepthTextureを取得する。 [SerializeField] private AROcclusionManager OcclusionManager; private void XXX() { var env_Texture = OcclusionManager.environmentDepthTexture; } RGB画像のサイズをDepth画像と合わせる RGB画像とDepth画像のサイズが一致していた方が扱い易い為Graphics.BlitをかけてRenderTextureに出力する。 private RenderTexture RGB_RT; private void XXX() { RGB_RT = RenderTexture.GetTemporary(Width, Height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); RGB_RT.Create(); Graphics.Blit(RGB_Texture, RGB_RT); } ShaderでDepth画像を加工する ARFoundationSample内にあるUnlit/DepthGradientシェーダーを使用してDepth画像を加工する。 [SerializeField] private Material EnvDepth_Material; private readonly int MaxDistanceId = Shader.PropertyToID("_MaxDistance"); private readonly int MinDistanceId = Shader.PropertyToID("_MinDistance"); public float EnvDepth_MinDistance { get; private set; } public float EnvDepth_MaxDistance { get; private set; } private RenderTexture EnvDepth_RT; private void OnEnable() { EnvDepth_MinDistance = 0; EnvDepth_MaxDistance = 5; EnvDepth_Material.SetFloat(MaxDistanceId, EnvDepth_MaxDistance); EnvDepth_Material.SetFloat(MinDistanceId, EnvDepth_MinDistance); } private void Update() { var env_Texture = OcclusionManager.environmentDepthTexture; if (EnvDepth_RT == null) { Width = env_Texture.width; Height = env_Texture.height; EnvDepth_RT = RenderTexture.GetTemporary(Width, Height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); EnvDepth_RT.Create(); } Graphics.Blit(env_Texture, EnvDepth_RT, EnvDepth_Material); } RenderTextureからピクセル情報を取得する RenderTextureから、Texture2Dにピクセルを反映しGetPixel32()によりColor32配列を取得。 その後、Color32配列よりbyte配列の方が保存等で扱い易い為変換する。 private Texture2D HubTexture; public byte[] RGB_Buffer { get; private set; } public byte[] EnvDepth_Buffer { get; private set; } private void Update() { if (HubTexture == null) HubTexture = new Texture2D(Width, Height); var currentRT = RenderTexture.active; RenderTexture.active = RGB_RT; HubTexture.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); HubTexture.Apply(); var colors = HubTexture.GetPixels32(); RGB_Buffer = Color32ArrayToByteArray(colors); RenderTexture.active = EnvDepth_RT; HubTexture.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); HubTexture.Apply(); colors = HubTexture.GetPixels32(); EnvDepth_Buffer = Color32ArrayToByteArray(colors); RenderTexture.active = currentRT; byte[] Color32ArrayToByteArray(Color32[] colors) { if (colors == null || colors.Length == 0) return null; int lengthOfColor32 = Marshal.SizeOf(typeof(Color32)); int length = lengthOfColor32 * colors.Length; byte[] bytes = new byte[length]; GCHandle handle = default(GCHandle); try { handle = GCHandle.Alloc(colors, GCHandleType.Pinned); IntPtr ptr = handle.AddrOfPinnedObject(); Marshal.Copy(ptr, bytes, 0, length); } finally { if (handle != default(GCHandle)) handle.Free(); } return bytes; } } Depth画像から距離を取得する Depth画像のbyte配列から距離情報のfloat配列を生成する。 Depth画像のbyte配列をColor32配列に割り当て、Color32のRGBをHSVに変換し、Hの値によって距離を割り出す。 void XXX() { var env_Colors = new Color32[env_Buffer.Length / 4]; var env_Distances = new float[env_Buffer.Length / 4]; for (int i = 0; i < EnvDepth_Buffer.Length / 4; i++) { int index = i * 4; env_Colors[i].r = EnvDepth_Buffer[index + 0]; env_Colors[i].g = EnvDepth_Buffer[index + 1]; env_Colors[i].b = EnvDepth_Buffer[index + 2]; env_Colors[i].a = EnvDepth_Buffer[index + 3]; Color.RGBToHSV(env_Colors[i], out float H, out float S, out float V); float distance = 0; if (H > 0.7f && H < 0.85f) { distance = 0; } else if (H >= 0 && H <= 0.7f) { distance = (0.7f - H) * (maxDistance - minDistance) / (0.7f + 0.15f); } else if (H >= 0.85f && H <= 1) { distance = (1.7f - H) * (maxDistance - minDistance) / (0.7f + 0.15f); } env_Distances[i] = distance; } } パーティクルを表示する RGBピクセル情報と距離情報を使用してパーティクルを生成する。 パーティクルのGameObjectは常にカメラの正面にある様にする。 ※RGB画像とDepth画像の向きが違うことに注意が必要 [SerializeField] private Transform ARCamera; [SerializeField] private ParticleSystem ParticleSystem; private ParticleSystem.Particle[] Particles; private readonly float ParticleSize = 0.025f; private void Awake() { GameObject particleObj = ParticleSystem.gameObject; particleObj.transform.SetParent(ARCamera); particleObj.transform.localPosition = Vector3.forward; } void XXX() { Array.Resize(ref Particles, pixelNum); int index = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float distance = env_Distances[index]; var _x = width - x - 1; int colorIndex = y * width + _x; //x軸反転 var r = RGB_Buffer[colorIndex * 4 + 0]; var g = RGB_Buffer[colorIndex * 4 + 1]; var b = RGB_Buffer[colorIndex * 4 + 2]; var a = RGB_Buffer[colorIndex * 4 + 3]; var particle = Particles[index]; particle.position = new Vector3(x * 0.01f, y * -0.01f, distance); var color = new Color32(r, g, b, a); particle.startColor = color; particle.startSize = ParticleSize; Particles[index] = particle; index++; } } float posX = -width * 0.01f / 2; float posY = height * 0.01f / 2; float posZ = ParticleSystem.gameObject.transform.localPosition.z; ParticleSystem.gameObject.transform.localPosition = new Vector3(posX, posY, posZ); ParticleSystem.SetParticles(Particles, Particles.Length); } まとめ こんな感じで動く。 プロジェクトのアプリは画面をタップすればRGBとPointCloudが切り替わるように実装した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SOLID原則を勉強する その4~インターフェース分離の原則(ISP)~

目次 SOLID原則を勉強する その1~単一責任の原則 (SRP)~ SOLID原則を勉強する その2~オープン・クローズド原則(OCP)~ SOLID原則を勉強する その3~リスコフの置換原則(LSP)~ SOLID原則を勉強する その4~インターフェース分離の原則(ISP)~ ←いまここ SOLID原則を勉強する その5~オ 依存性逆転の原則(DIP)~ 前置き 書籍を読んだり、ググったりして、自分に分かりやすいようにまとめた記事です。 より詳しく知りたい方は、下記の参考文献を読んでみてください。 参考文献 Clean Architecture 達人に学ぶソフトウェアの構造と設計 | Amazon Adaptive Code ~ C#実践開発手法 | Amazon C#の設計の基本【SOLID原則】まとめ Unity開発で使える設計の話+Zenjectの紹介 C# で SOLID の原則に違反する危険性 インターフェース分離の原則(ISP) クライアントが利用しないメソッドの実装を強制してはいけない 「不必要なメソッドなのに実装しなければならない」を防ぐ インターフェースには最小限のものだけ定義するべき コード例 それぞれの敵の行動パターンを管理する Shadow クラス、 Gigas クラス、 DarkInferno クラスを作成します。 (敵の元ネタはキングダムハーツです。分からない人はすみません…) それぞれのクラスは、以下の行動を行います。 Shadow Gigas DarkInferno 攻撃 ○ ○ ○ 歩行 ○ ○ × 飛行 × ○ ○ before IEnemy インターフェースを用意 敵クラスは全てこのインターフェースを実装する IEnemy.cs public interface IEnemy { void Attack(); void Walk(); void Fly(); } Shadow.cs using UnityEngine; public class Shadow : IEnemy { public void Attack() { Debug.Log("シャドウの攻撃"); } public void Fly() { // 空を飛ばないので、このメソッドでは何もしない } public void Walk() { Debug.Log("シャドウが歩いて移動"); } } Gigas.cs using UnityEngine; public class Gigas : IEnemy { public void Attack() { Debug.Log("ギガースの攻撃"); } public void Fly() { Debug.Log("ギガースが空を飛ぶ"); } public void Walk() { Debug.Log("ギガースが歩く"); } } DarkInferno.cs using UnityEngine; public class DarkInferno : IEnemy { public void Attack() { Debug.Log("ダークインフェルノの攻撃"); } public void Fly() { Debug.Log("ダークインフェルノが空を飛んで移動"); } public void Walk() { // 歩かずに常に空を飛んで移動するので、このメソッドでは何もしない } } ダメなところ Gigas クラスは全てのメソッドを必要としているが… Shadow クラスは Fly メソッドは不要(空を飛ばないから) DarkInferno クラスは Walk メソッドは不要(歩かないから) 不要なメソッドの実装を強要している 新たな敵クラスを実装するときや、メソッドを呼び出すクライアント側も混乱してしまう After IEnemy インターフェースを IAttack, IWalk, IFly の各インターフェースに分離 敵クラスは必要なインターフェースを選んで実装する IAttack.cs public interface IAttack { void Attack(); } IWalk.cs public interface IWalk { void Walk(); } IFly.cs public interface IFly { void Fly(); } Shadow.cs using UnityEngine; public class Shadow : IAttack, IWalk { public void Attack() { Debug.Log("シャドウの攻撃"); } public void Walk() { Debug.Log("シャドウが歩いて移動"); } } Gigas.cs using UnityEngine; public class Gigas : IAttack, IFly, IWalk { public void Attack() { Debug.Log("ギガースの攻撃"); } public void Fly() { Debug.Log("ギガースが空を飛ぶ"); } public void Walk() { Debug.Log("ギガースが歩く"); } } DarkInferno.cs using UnityEngine; public class DarkInferno : IAttack, IFly { public void Attack() { Debug.Log("ダークインフェルノの攻撃"); } public void Fly() { Debug.Log("ダークインフェルノが空を飛んで移動"); } } 良いところ 不要なインターフェースの実装がなくなった 終わりに もし変なところがあったら教えて下さい。 (特にクラス図とか…)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ボクシング(ボックス化)のパフォーマンスを検証してみた。

はじめに 以前転職活動をしたときに面接官(たぶん現役のバリバリの技術屋さん)に「ボクシングって知っていますか?」と聞かれてスポーツのボクシングしか頭に浮かばなかったことを思い出したのでお勉強してみました。(/・ω・)/ (本来ならばその場で調べるべきではあるのですが...) ボクシング(ボックス化)とは? ボクシング(ボックス化)とは、値型をobject型などの参照型に代入した際に値型から参照型へ変換する処理のことを指します。 どうやらボクシングは重い処理らしい プログラムでは変数等を一時的にメモリで管理し、値型はスタックと呼ばれる領域で、参照型はヒープと呼ばれる領域で管理を行います。 ヒープ領域の確保はスタック領域にある値をヒープ領域へコピーし、アドレスや型情報をスタック領域へ格納するという段階を踏むためにスタック領域の確保と比較すると重たい処理となります。 (以下の2つの記事がとても分かりやすかったです。ありがとうございます!) 実際に検証してみる どれくらい速度が変わるのか試してみました。 以下はobject型(参照型)へint型の値を代入する処理とint型(値型)へint型の値を代入する処理を比較しています。 前者ではボクシング(ボックス化)が発生しています。 処理時間計測は以下の記事を参考にさせていただきました。ありがとうございました! サンプルコード Program.cs static void Main(string[] args) { object a; int b; var sw = new System.Diagnostics.Stopwatch(); sw.Start(); //20億回ボクシングが発生 for(var i = 0; i < 2000000000; i++) { a = 5; } sw.Stop(); TimeSpan ts = sw.Elapsed; Console.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒"); var sw2 = new System.Diagnostics.Stopwatch(); sw2.Start(); //こちらはそのままint型へ値型を代入 for (var i = 0; i < 2000000000; i++) { b = 5; } sw2.Stop(); TimeSpan ts2 = sw2.Elapsed; Console.WriteLine($" {ts2.Hours}時間 {ts2.Minutes}分 {ts2.Seconds}秒 {ts2.Milliseconds}ミリ秒"); } 結果 0時間 0分 9秒 664ミリ秒 <-- ボクシングが発生していた部分 0時間 0分 3秒 334ミリ秒 <-- ボクシングは発生していない部分 お、おお~~。 約3倍くらいですかね。 確かにボクシングが重い処理であることが分かりました。 おわりに とはいったものの、原理はたぶん分かったのですが。。。 むむむ。。。あれれ?日常のどこでこれを意識するのだ。。。となったので次はその段階へチャレンジしたいと思います。 分かったら追記するぞ~!!! 【2021年8月11日追記】 お客様先の上司に教えてもらいました~!!感謝!! どうやらボックス化はC#1.0~C#2.0の頃に話題になったようですね~。 例えばSystem.Collections.ArrayListでは格納できるものがObject型だったため、参照型の時は問題ないのですがintなどの値型を入れる際にボックス化が発生してパフォーマンスに影響が出てしまうようでした。 その影響を解消するために出てきたのがジェネリック版であるListだったのですね(System.Collections.Generic)。 ボックス化は今ではだいぶ解消されていますがEqualsなどの修正がされていない(修正が困難)ものもあるのでそれを使うときはボクシングを意識する必要がありそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む