質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

Q&A

解決済

5回答

3429閲覧

【C#】動的List(dynamic)内でsortを実行したい

inari_ken

総合スコア34

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

0グッド

1クリップ

投稿2020/03/23 05:14

編集2020/03/24 01:44

こんにちは。今回もよろしくお願いします。

前提・実現したいこと

下図の構成のようなListがあります。(LINQで作成)
構成は維持したまま、c[]内のresult 全てに対して、Choiseの値をキーにして昇順にソートを掛けたいのですが
エラーとなってしまいます。
式ツリー型にキャストすれば解決するのではという予測はついているのですがどのようにキャストするのか見当が付きません。
イメージ説明

該当のソースコード

C#

1 c.ForEach(x => x.result.Sort((a,b) => string.Compare(a.Choise,b.Choise))); 2

発生している問題・エラーメッセージ

ラムダ式を最初にデリゲート型または式ツリー型にキャストせずに、動的にディスパッチされる操作の引数として使用することはできません ※(a,b) => string.Compare(a.Choise,b.Choise) 部分に波線

補足

LINQで求めているなら、orderbyをすればよいのでは と思われるかもしれませんが、
LINQで求めたのちにListに対してadd処理を行っている為、再度sortしなおす必要があると考えています。

2020/3/23/16:58追記

より本番環境に近い形のサンプルコードを作成しました。

C#

1 private void test() 2 { 3 4 List<string[]> data1 = new List<string[]>(); 5 6 7 List<dynamic> c = new List<dynamic>(); 8 9 10 data1.Add(new string[2] { "1.aaa", "2.aaa" }); 11 data1.Add(new string[2] { "3.bbb", "2.bbb" }); 12 data1.Add(new string[2] { "2.ccc", "1.ccc" }); 13 14 15 int item_count = 0; 16 for (int j = 0; j < 2; j++) 17 { 18 19 c.Add(new 20 { 21 title = j.ToString() + ".test", 22 result = data1.GroupBy(x => x[item_count]) 23 .Select(x => new { Choise = x.Key, Count = x.Count() }) 24 .ToList() 25 }); 26 27 item_count++; 28 29 } 30 31 foreach (var item in c) 32 { 33 List<dynamic> list = item.result; 34 list.Sort((a, b) => string.Compare(a.Choise, b.Choise)); 35 } 36 }

2020/3/23/14:55追記 (廃止) 

以下誤りです.
ご指摘により該当のListを作成するコードを作成しました。

C#

1 private void test() 2 { 3 4 List<dynamic> c = new List<dynamic>() { 5 new { 6 result = new List<dynamic>() 7 { 8 new { Choise ="01.やまだ", Count = 1}, 9 new { Choise ="03.さとう", Count = 3}, 10 new { Choise ="02.やまもと", Count = 4} 11 }, 12 title = "1.test" 13 14 }, 15 new { 16 result = new List<dynamic>() 17 { 18 new { Choise ="01.aaa", Count = 1}, 19 new { Choise ="03.bbb", Count = 3}, 20 new { Choise ="02.ccc", Count = 4} 21 }, 22 title = "2.test" 23 } 24 }; 25 26 // c.ForEach(x => x.result.Sort((a, b) => string.Compare(a.Choise, b.Choise))); 27 28 29 }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2020/03/23 05:43

同じリストを回答者の手元で作れる短いコードを作って載せると解決が早いでしょう。
inari_ken

2020/03/23 05:53

Zuishin様 ご指摘ありがとうございます。スモールコード作成しますので少々お待ちください
inari_ken

2020/03/23 05:57

Zuishin様 作成しました。
BluOxy

2020/03/23 06:39 編集

dynamic型ではない静的な型を値(掲示されたコードでいうc)に持たせるのは難しいでしょうか。(データの取得元はJSONか何かでしょうか) 追記:動的か静的かはこの質問の本題ではなさそうなので、スルーでお願いします
Zuishin

2020/03/23 06:34

これならわかりやすいです。誰か答えてくれるでしょう。こんなことはないと思いますが、もし長期間回答がつかないようなら私も試してみます。 LINQ 一発では無理で、ループ中で result を List<dynamic> にキャストしてソートするというのを result の回数だけ行えばできそうに思います。
BluOxy

2020/03/24 05:04 編集

> 既存プログラムの大部分で共用 本題から離れていると承知の上でコメントしますが、稼働中のプログラムだからこそdynamicはやめてクラスなり匿名型なりValueTupleなりに変更する方が良いと思います。これではコンパイルエラーが起きないので、未然にバグを防げません。 > 元の構成を変えてしまうと修正と検証に工数がかかってしまうため、本件に関しては構成を維持したままというところを前提とさせていただければと思います。 例えcのコレクションが持つ要素の型が静的な型に変わっても、他の参照箇所が正しく動いていたならコンパイルエラーは発生しないはずです。(つまり、影響としてはほとんどゼロの認識) もし発生するならそこは元々正しく動かなかっただけであり、dynamicを使ったことによる弊害が出ていることになります。 この弊害をスルーすることによって今後技術的な負債を抱えるリスクがある。そのリスク対応に対して今dynamicを直す以上の工数がかかる可能性がある。ということを念頭に入れた上で構成を維持すべきかどうかは判断すべきだと思います。
guest

回答5

0

ベストアンサー

dynamicをそのまま放置することになる意識の低い回答ですが参考程度に。

ラムダ式を最初にデリゲート型または式ツリー型にキャストせずに、動的にディスパッチされる操作の引数として使用することはできません

のエラーメッセージで示されているデリゲート型にキャストのパターンです。

C#

1Comparison<dynamic> dynamicSortComparison = 2 (dynamic a, dynamic b) => string.Compare(a.Choise, b.Choise); 3foreach (var item in c) 4{ 5 item.result.Sort(dynamicSortComparison); 6}

投稿2020/03/24 11:14

imihito

総合スコア2166

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2020/03/24 11:17

これでできるんですね。
inari_ken

2020/03/25 06:42

ご回答ありがとうございました。 いろいろと皆様のご回答でパターンを学べました。 これから他の方のご回答も一つ一つ試してどういった動作をするのか確認していきます。 ベストアンサーを決めるには私の知見が足りなさすぎて恐縮ですが、 最小コードで済んでいることと、ほぼ質問文のコードの原型を残していること 動作確認も完了したことから こちらの方をベストアンサーとさせて頂きます。 皆様のご回答、ありがとうございました。 取り急ぎ御礼まで。
guest

0

匿名型と dynamic が入り混じり、共変性反変性の問題もあるので手ごわいと思います。
何も考えず Comparison<T> を式木で組み立てて使いましたが、IComparer<T> の方がもしかすると簡単にできたかもしれません。

他のコードはともかく、できるところから dynamic を排除していくのをお勧めします。多用するものではありません。あと、匿名型を使う必然性も全く感じられません。これだけ再利用するのであれば普通に名前をつけたらいいと思います。

C#

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Linq; 5using System.Linq.Expressions; 6 7namespace ConsoleApp1 8{ 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 test(); 14 } 15 16 private static void test() 17 { 18 19 List<string[]> data1 = new List<string[]>(); 20 21 22 List<dynamic> c = new List<dynamic>(); 23 24 25 data1.Add(new string[2] { "1.aaa", "2.aaa" }); 26 data1.Add(new string[2] { "3.bbb", "2.bbb" }); 27 data1.Add(new string[2] { "2.ccc", "1.ccc" }); 28 29 30 int item_count = 0; 31 for (int j = 0; j < 2; j++) 32 { 33 34 c.Add(new 35 { 36 title = j.ToString() + ".test", 37 result = data1.GroupBy(x => x[item_count]) 38 .Select(x => new { Choise = x.Key, Count = x.Count() }) 39 .ToList() 40 }); 41 42 item_count++; 43 44 } 45 46 var itemType = new { Choise = "", Count = 0 }.GetType(); 47 var comparisonType = typeof(Comparison<>) 48 .MakeGenericType(itemType); 49 var compareMethod = typeof(string) 50 .GetMethod("Compare", new[] { typeof(string), typeof(string) }); 51 var param1 = Expression.Parameter(itemType); 52 var param2 = Expression.Parameter(itemType); 53 var choise = itemType.GetMethod("get_Choise"); 54 var compare = Expression.Lambda( 55 comparisonType, 56 Expression.Call( 57 null, 58 compareMethod, 59 Expression.Call(param1, choise), 60 Expression.Call(param2, choise)), 61 param1, 62 param2 63 ).Compile(); 64 65 var listType = typeof(List<>) 66 .MakeGenericType(itemType); 67 var sortMethod = listType.GetMethod("Sort", new[] { comparisonType }); 68 69 foreach (var item in c) 70 { 71 foreach (var result in item.result) 72 { 73 Console.WriteLine(result); 74 } 75 } 76 Console.WriteLine("-----"); 77 78 foreach (var item in c) 79 { 80 sortMethod.Invoke(item.result, new[] { compare }); 81 } 82 83 foreach (var item in c) 84 { 85 foreach (var result in item.result) 86 { 87 Console.WriteLine(result); 88 } 89 } 90 } 91 } 92}

追記 IComparer<T> を使う場合(未検証)

C#

1var comparer = new Comparer(new { Choise = "", Count = 0 }); 2item.result.Sort(comparer); 3 4class Comparer<T> : IComparer<T> 5{ 6 public Comparer(T dummy) {} 7 8 public int Compare([AllowNull] T x, [AllowNull] T y) 9 { 10 return string.Compare(((dynamic)x).Choise, ((dynamic)y).Choise); 11 } 12}

追記

C#

1IComparer と同様ダミーが気持ち悪いですが、これでキャストできますね。 2 3List<T> ListFromDynamic<T>(dynamic list, T dummy) 4{ 5 return (List<T>)list; 6}

投稿2020/03/24 03:22

編集2020/03/24 08:35
Zuishin

総合スコア28656

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

inari_ken

2020/03/25 06:36

ご回答ありがとうございました。 内容咀嚼します。 取り急ぎ御礼まで。
guest

0

sortを実行したい

については必要な時にOrderByをしたコレクションが返せれば必要十分に感じます。
あとdynamic 反対派です。

サンプルコードを用意しました。ポイントは以下の3つです。

  • コレクションの要素をクラスに変更する
  • クラスに並び替えたコレクションを取得するためのメソッドを定義する
  • 使う側は必要な際にそのメソッドを呼び出す

C#

1using System.Collections.Generic; 2using System.Linq; 3using System; 4 5public class Program{ 6 public static void Main(){ 7 test(); 8 } 9 10 private static void test() 11 { 12 List<Data> c = new List<Data>() { 13 new Data() 14 { 15 result = new List<Data2>() 16 { 17 new Data2 { Choise ="01.やまだ", Count = 1}, 18 new Data2 { Choise ="03.さとう", Count = 3}, 19 new Data2 { Choise ="02.やまもと", Count = 4} 20 }, 21 title = "1.test" 22 }, 23 new Data() 24 { 25 result = new List<Data2>() 26 { 27 new Data2 { Choise ="01.aaa", Count = 1}, 28 new Data2 { Choise ="03.bbb", Count = 3}, 29 new Data2 { Choise ="02.ccc", Count = 4} 30 }, 31 title = "2.test" 32 } 33 }; 34 foreach(var data in c){ 35 foreach(var result in data.GetSortedResults()){ 36 Console.WriteLine(result.Choise); 37 } 38 Console.WriteLine("----"); 39 } 40 } 41} 42 43//TODO:クラス名は適切な名称に変更する 44public class Data{ 45 public string title { get; set; } 46 public List<Data2> result { get; set; } 47 public IOrderedEnumerable<Data2> GetSortedResults(){ 48 return result.OrderBy(x => x.Choise); 49 } 50} 51 52public class Data2{ 53 public string Choise { get; set; } 54 public int Count { get; set; } 55}

投稿2020/03/24 03:59

編集2020/03/24 04:11
BluOxy

総合スコア2663

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

inari_ken

2020/03/25 06:36

ご回答ありがとうございました。 内容咀嚼します。 取り急ぎ御礼まで。
guest

0

まずそんなことに dynamic を使っちゃ駄目です。
匿名型かValueTupleを使いましょう。

C#

1var c = new[] 2{ 3 new 4 { 5 result = new[] 6 { 7 new { Choise ="01.やまだ", Count = 1}, 8 new { Choise ="03.さとう", Count = 3}, 9 new { Choise ="02.やまもと", Count = 4} 10 }.ToList(), 11 title = "1.test" 12 }, 13 new 14 { 15 result = new[] 16 { 17 new { Choise ="01.aaa", Count = 1}, 18 new { Choise ="03.bbb", Count = 3}, 19 new { Choise ="02.ccc", Count = 4} 20 }.ToList(), 21 title = "2.test" 22 } 23}.ToList();

投稿2020/03/23 08:02

hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

inari_ken

2020/03/23 08:16

大変申し訳ございません。 提示したサンプルコードに誤りがあったため、再度質問を修正致しました。 ご指摘ありがとうございます。次回から作成するプログラムからに関しては安易にdynamicを使用しない設計にする予定です。 ただ、このListは既存プログラムの大部分で共用しており、元の構成を変えてしまうと修正と検証に工数がかかってしまうため、本件に関しては構成を維持したまま というところを前提とさせていただければと思います。 なお、こうなってしまった背景は 実際は古い方のサンプルコードのようにnewをする形でListは作成しておらず 各項目ごとの配列を、その項目の集計結果(LINQ)+項目タイトル名という形で保持しておきたかったためです。
guest

0

Sortを使うためには、item.resultがList型で有ることを明示しないと、どのSort関数なのかわかりません。
そのため、ラムダ式のa, bの型も確定せず、エラーが出ているものだと思われます。

item.resultの型を確定させてやればいけました

foreach (var item in c) { List<dynamic> list = item.result; list.Sort((a,b) => string.Compare(a.Choise, b.Choise)); }

投稿2020/03/23 06:55

編集2020/03/23 06:59
izmktr

総合スコア2856

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

inari_ken

2020/03/23 08:04

大変申し訳ありません。 ご回答いただいた内容で動作することは確認できたのですが サンプルコードのListの生成方法が誤っていたため、本番環境では動作しませんでした。 お手数おかけしますが、サンプルコードをより本番環境に近い形で作成しましたので ご確認頂けますと幸いです。 ちなみに、本番環境でのエラー内容は下記となります。 'System.Collections.Generic.List<<>f__AnonymousType0<string,int>>' を 'System.Collections.Generic.List<object>' に変換できません'
izmktr

2020/03/23 08:08

この解答を見て、何が悪いのかわからない程度の理解度や、 自分のコードに合わせて書き換える技量がないのなら、 dynamicは使うべきではありません 素直に、dynamicを使っている部分をclassを生成して作り直すべきです
Zuishin

2020/03/24 04:21 編集

そこまでの技量を求めるのは求めすぎじゃないでしょうか。List<dynamic> を List<匿名型> に書き換えるのはなかなか大変です。List<T> の場合 IEnumerable<T> と違って変性注釈が無いため、ポリモーフィズムで解決できません。よって一から書き換えねばならず、この回答のコードは参考になりません。
izmktr

2020/03/24 05:03

私ならこの人が匿名型を使うことも反対しますよ
Zuishin

2020/03/24 05:10

だいたい全員反対してますね。その件とは別問題です。 この回答を見て、何が悪いのかわかる「程度」の理解度や自分のコードに合わせて書き換える技量というのは結構レベルが高いという意味です。
Zuishin

2020/03/24 05:16

誤解があるかもしれないので補足ですが、質問のコードは List<匿名型> なので List<dynamic> にキャストできません。そして匿名型は名前がないので List<匿名型> にキャストするのは困難です。よってこのコードから大幅に変更しなければならず、それができるレベルの人にはこのコードは必要ありません。 ということです。
izmktr

2020/03/24 05:27

フレームのもとなんでやめてくれませんか? dynamicなんてコンパイルエラーが出ず、実行時エラーの世界なんだから、 コンパイルエラーで躓かない程度の技量は求めますよ
Zuishin

2020/03/24 06:03 編集

なぜ自分でなく私がフレームの元なのか理解に苦しみますが、コンパイルエラーでつまずかない人に差し出す回答ではありませんね。私もコンパイルエラーでつまずく程度の技量です。
inari_ken

2020/03/25 06:36

ご回答ありがとうございました。 内容咀嚼します。 取り急ぎ御礼まで。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問