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

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

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

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

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

Q&A

解決済

2回答

229閲覧

多重ソート「いろは」の「は」。Func<T, objet>を構築したい

meshkit

総合スコア72

C#

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

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

0グッド

0クリップ

投稿2018/03/02 07:29

編集2018/03/02 08:22

###「いろは」の「ろ」

Models .OrderBy(cell => cell.Number) .ThenBy(cell => cell.Size);

でNumberとSizeがnullの場合、nullを下にするようにしました。

Models .OrderByDescending(cell => !string.IsNullOrEmpty(cell.Number)) .ThenBy(cell => cell.Number) .ThenByDescending(cell => !string.IsNullOrEmpty(cell.Size)) .ThenBy(cell => cell.Size);

###「いろは」の「は」
これソート項目が増えると大変なので。
https://www.gakusmemo.com/?p=197

C#

1public static IEnumerable<T> ConcatenationSort<T>(this IEnumerable<T> query, List<string> sortitems) 2{ 3 int index = 0; 4 IOrderedEnumerable<T> orderedQuery = null; 5 foreach (var sortitem in sortitems) 6 { 7 Func<T, object> expression = item => item.GetType() 8 .GetProperty(sortitem) 9 .GetValue(item, null); 10 orderedQuery = (index == 0) ? query.OrderBy(expression) : orderedQuery.ThenBy(expression); 11 index++; 12 } 13 query = orderedQuery; 14 return query; 15}

これだと、

Models .ConcatenationSort(new List<string> { "Number", "Color", "Size" });

でいけます。
ただ、

C#

1orderedQuery = (index == 0) ? query.OrderBy(expression) : orderedQuery.ThenBy(expression);

のとおり、null対応していません。
null対応するには、

C#

1orderedQuery = (index == 0) ? query.OrderByDescending(expression2).OrderBy(expression) : orderedQuery.ThenByDescending(expression2).ThenBy(expression);

とすると思います。
このexpression2をどう構築すればいいですか?

C#

1 Func<T, object> expression2 = item => !string.IsNullOrEmpty(item.GetType() 2 .GetProperty(sortitem) 3 .GetValue(item, null) 4 .ToString());

と作ってみたところ、nullになって動かずにいます。

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

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

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

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

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

KojiDoi

2018/03/02 07:36

タイトルは質問趣旨を分かりやすく反映したものにしていただけませんかね。
Zuishin

2018/03/02 08:19 編集

.ThenByDescending(cell => cell.Size).ThenBy(cell => cell.Size); 意味の無い並べ替えではありませんか?
meshkit

2018/03/02 08:22

.ThenByDescending(cell => !string.IsNullOrEmpty(cell.Size))でした。
guest

回答2

0

ベストアンサー

hihijiji さんのおっしゃる通り、Number や Size を string にするのは悪手です。
なぜなら int ならもちろん 2 < 10 となりますが、string だと "2" > "10" になります。
一文字目の '2' が '1' より大きいからです。
それを踏まえて List<T>.Sort(Comparison<T>) で作ってみました。

C#

1public class Model 2{ 3 public int? Number { get; set; } 4 public int? Size { get; set; } 5 public Color? Color { get; set; } 6 7 public override string ToString() 8 { 9 return $"Number={Number?.ToString() ?? "null"}, Size={Size?.ToString() ?? "null"}, Color={Color?.ToString() ?? "null"}"; 10 } 11} 12 13public static IEnumerable<T> ConcatenationSort<T>(this IEnumerable<T> query, IEnumerable<string> sortitems) 14{ 15 var list = query.ToList(); 16 var properties = sortitems 17 .Select(a => typeof(T).GetProperty(a)) 18 .ToList(); 19 Comparison<T> comparison = (x, y) => 20 { 21 if (ReferenceEquals(x, null)) return 1; 22 if (ReferenceEquals(y, null)) return -1; 23 foreach (var property in properties) 24 { 25 var xp = property.GetValue(x); 26 if (ReferenceEquals(xp, null)) return 1; 27 var yp = property.GetValue(y); 28 if (ReferenceEquals(yp, null)) return -1; 29 if (xp is string) 30 { 31 if ((string)xp.Length == 0) return 1; 32 if ((string)yp.Length == 0) return -1; 33 } 34 var comparable = xp as IComparable; 35 if (comparable == null) continue; 36 int result = comparable.CompareTo(yp); 37 if (result == 0) continue; 38 return result; 39 } 40 return 0; 41 }; 42 list.Sort(comparison); 43 return list; 44}

投稿2018/03/02 11:14

編集2018/03/02 11:35
Zuishin

総合スコア28660

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

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

meshkit

2018/03/05 00:37

コメントありがとうございます。わたしもhihijijiさんのコードだけ見て「なるほど」と一瞬思ったのですが、そもそもSizeは数字だけでなく、SMLとかの文字もあるので、数字にParseできないです。桁数も20桁とかで、連携しているDBがvarcharなので、stringで扱うしかないです。 コードありがとうございます。ほぼこれに近いコードを最初に書きました。 OrderByを使うよりも高速で、そういう意味では、わざわざより低速なOrderByを使う必要はほとんどないのですが、それでも質問したのは、LINQの勉強になるのでOrderByを使ってみたいこと、今回のシステムではソートするのは出力のとき1回だけなので、速度低下は(たぶん)あまり重大な課題にはならないだろうこと(なったら直せばいい、すでにコードはあるから)、見かけたコードがcoolに見えた、などの理由です。
Zuishin

2018/03/05 00:49

挙げられているのは Enumerable.OrderBy<TSource, TKey> メソッド (IEnumerable<TSource>, Func<TSource, TKey>) ですが、これはデフォルトの IComparable を使っていますので null の順序を変えることはできません。 ですからこれを少し変えて作ろうと考えると迷宮入りします。 Enumerable.OrderBy<TSource, TKey> メソッド (IEnumerable<TSource>, Func<TSource, TKey>, IComparer<TKey>) を使ってください。ただし IComparer を実装した比較用クラスを自分で作る必要があります。 比較用クラスの中身は「ほぼこれに近いコード」でできます。 クールかどうかはともかく。
Zuishin

2018/03/05 00:52

なお IComparable<T> と IComparer<T> は全く別のインターフェースなので注意してください。前者はデフォルトの並び順を指定するもので、後者は並び順を動的に変更するのに使います。
meshkit

2018/03/05 01:16

了解しました。ありがとうございます!
Zuishin

2018/03/05 04:57

今更ですが、OrderBy(cell => cell.Number == null ? 1 : 0).ThenBy(cell => cell.Number) で多分できますね。頭が固くなっていたようです。
meshkit

2018/03/07 01:10

ありがとうございます! 今度試してみます。
guest

0

そんな変なことはせず、並べ替えたい型を作って IComparable か IComparable<T>を実装してください。
Number と言うプロパティを作ったらその型は整数型かそのNull許容型にしてください。

サンプルを書いてみました。

C#

1namespace ConsoleApp1 2{ 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 var sample = new[] 8 { 9 new SampleClass 10 { 11 Number = 10, 12 Point = new Point 13 { 14 X = 1, 15 Y = 10 16 }, 17 Color = ConsoleColor.Black 18 }, 19 new SampleClass 20 { 21 Point = new Point 22 { 23 X = 5, 24 Y = 5 25 } 26 }, 27 new SampleClass 28 { 29 Number = 5, 30 Color = ConsoleColor.Cyan 31 } 32 }; 33 34 var sortedSample = sample 35 .OrderBy(s => s.Number ?? int.MaxValue) 36 .ThenBy(s => s.Point) 37 .ThenBy(s => s.Color == null ? int.MaxValue : (int)s.Color); 38 39 } 40 } 41 42 class SampleClass 43 { 44 public int ? Number { get; set; } 45 46 public Point Point { get; set; } 47 48 public ConsoleColor? Color { get; set; } 49 } 50 51 public class Point : IComparable<Point> 52 { 53 public int X { get; set; } 54 public int Y { get; set; } 55 56 public int CompareTo(Point other) 57 { 58 if (other == null) return 1; 59 return (this.X + this.Y) - (other.X + other.Y); 60 } 61 } 62 63}

投稿2018/03/02 08:04

編集2018/03/02 08:51
hihijiji

総合スコア4150

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

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

meshkit

2018/03/02 08:32

ありがとうございます。 IComparableの実装とはどんな感じでしょう? まったく初耳で、見当がつきません。
meshkit

2018/03/05 00:28

サンプルありがとうございます。 なるほど、int.MaxValueか! と一瞬思ったものの、桁あふれしてしまうことと、連携しているDBがvarcharなので、stringです。 float.MaxValueとかもありなのかもしれないのですが、float.Parse(value)とかも必要になり、DBとどんどん離れてしまうのです。 そもそもSizeは数字だけでなく、SMLとかの文字もあるので、数字にParseできないです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問