前回の【無駄話】AI vs. AI-私の勉強法(1) et. seq. の通り、DataGridViewの列ヘッダークリックによるソートの為に、Listのソートについて学ぶために、クラスオブジェクトの同等判定、比較判定について復習(と言うか、学習...)しましたので、その結果を備忘しておきます。(注)
注:Windows 11標準装備の.NET Framework 4.8のC# 5.0ではデリゲートの'=>'、Nullableの'?'や'is null'演算子が使えませんので、それらは使っていません。
クラスオブジェクトを宣言、定義した場合、既定となる同等判定は
(1)Equalsメソッド
(2)ReferenceEqualsメソッド及び
(3)'=='演算子
となりますが、これらは全て参照比較(注)となり、「完全に同じインスタンス」でないと全て偽(false)になります。
このため、(アドレス比較を目的としたReferenceEqualsメソッド以外の)Equalsメソッドと'=='演算子について、「クラスメンバーの値が同等の場合、同等とする」仕様であれば、ユーザーがそのように実装する必要があります。
注:クラスという設計図に基づき、メモリーに作られた実体<インスタンス>のメモリーアドレスを比較すること。
次にクラスオブジェクトの比較判定を行うには、クラス宣言の際に": ICompare<(クラス名)>"を付加すれば、既定でCompareToメソッドが使われます。但しC#の仕様から比較対象要素は一つとなるので、複数の異なる要素で比較しようとする場合、それぞれ別のメソッドを設ける必要があります。(そしてその場合谷津のCompareToメソッドを使う訳ではないので、クラス宣言に必ずしも": ICompare<(クラス名)>"を付加する必要はありません。)
サンプルのコンソールプログラムを組んでみましたので、ご参考までに以下に掲示します。
【Equals_Comparison.cs】
///////////////////////////////////
// Test program for Class Objects'
// Equals Method and
// Comparison Method
///////////////////////////////////
using System;
namespace Equals_Comparison
{
public class TestObject_Org : IComparable<TestObject_Org>
{
public string Name {get; set;}
public int Val {get; set;}
/*
「既定のObjectの同等比較」
比較方法 既定の内部動作 Equals を修正した場合の挙動
--------------------------------------
ReferenceEquals 常にメモリーアドレスを比較 変更不可(常に参照比較)
Equals メソッド 既定は参照比較(同上) 値比較への変更可能
== 演算子 既定は参照比較 自動では変わらない
<参考-C# 7.0以降>
'is null'演算子 ReferenceEquals(対象Obj, null)と等価
("== null"は等価ではない)
*/
//比較メソッドの定義
public int CompareTo(TestObject_Org other)
{
if(ReferenceEquals(other, null)) //null参照比較
return 1; //thisが大きい
return this.Val.CompareTo(other.Val); //Valで比較:0なら等しい、負なら小さい、正なら大きい
}
public static bool operator <(TestObject_Org left, TestObject_Org right)
{
//leftがnullの場合
if(ReferenceEquals(left, null))
return !ReferenceEquals(right, null); //rightがnullでなければ「左 < 右」はtrue、両方nullなら false
//leftが nullでなければ、内蔵のCompareToに判定を委ねる
return left.CompareTo(right) < 0;
}
public static bool operator >(TestObject_Org left, TestObject_Org right)
{
//leftがnullの場合は絶対に「右より大きい」にはならない
if(ReferenceEquals(left, null))
return false;
//leftが nullでなければ、内蔵のCompareToに判定を委ねる
return left.CompareTo(right) > 0;
}
public static bool operator <=(TestObject_Org left, TestObject_Org right)
{
return ReferenceEquals(left, null) || left.CompareTo(right) <= 0;
}
public static bool operator >=(TestObject_Org left, TestObject_Org right)
{
if (ReferenceEquals(left, null))
return ReferenceEquals(right, null); //両方nullのときだけtrue
return left.CompareTo(right) >= 0;
}
}
public class TestObject_Rev
{
public string Name {get; set;}
public int Val {get; set;}
//Equalsメソッドに値比較を追加
public override bool Equals(object obj)
{
if(ReferenceEquals(obj, null)) //null参照比較
return false;
if(obj.GetType() != typeof(TestObject_Rev)) //型相違
return false;
return (this.Name == ((TestObject_Rev)obj).Name &&
this.Val == ((TestObject_Rev)obj).Val);
}
//GetHashCodeメソッドの拡張(EqualsのNameとValの比較の為に必要)
public override int GetHashCode()
{
//null参照による例外を防ぐためのチェック(intは初期値'0'が入る)
int hashName = (this.Name != null) ? this.Name.GetHashCode() : 0;
int hashVal = this.Val.GetHashCode();
//2つのハッシュ値を掛け算と足し算で合成
//397(素数)を乗じ、値の偏り(衝突)を抑制
unchecked
{
int hash = 17;
hash = (hash * 397) ^ hashName;
hash = (hash * 397) ^ hashVal;
return hash;
}
}
//演算子'=='の定義
public static bool operator ==(TestObject_Rev left, TestObject_Rev right)
{
if(ReferenceEquals(left, right)) //同一オブジェクト
return true;
else
if(ReferenceEquals(left, null) || ReferenceEquals(right, null)) //何れか(または両方)がnull
return false;
return left.Equals(right); //それ以外の場合は比較
}
//演算子'!='の定義
public static bool operator !=(TestObject_Rev left, TestObject_Rev right)
{
return !(left == right); //単なる'=='の否定
}
//比較メソッドの定義
public int CompareNameTo(TestObject_Rev other)
{
if(ReferenceEquals(other, null)) //null参照比較
return 1; //thisが大きい
return this.Name.CompareTo(other.Name); //0なら等しい、負なら小さい、正なら大きい
}
public int CompareValTo(TestObject_Rev other)
{
if(ReferenceEquals(other, null)) //null参照比較
return 1; //thisが大きい
return this.Val.CompareTo(other.Val); //0なら等しい、負なら小さい、正なら大きい
}
public static void Main()
{
//Testing TesatObject_Org as to if two are same
TestObject_Org test1 = new TestObject_Org() {Name = "Ysama", Val = 1};
TestObject_Org test2 = new TestObject_Org() {Name = "Ysama", Val = 1};
Console.WriteLine("TestObject_Org test1 : Name = \"Ysama\", Val = 1");
Console.WriteLine("TestObject_Org test2 : Name = \"Ysama\", Val = 1");
Console.WriteLine("TestObject_Org test1 == test2 : {0}", test1 == test2);
Console.WriteLine("TestObject_Org test1.Equals(test2) : {0}", test1.Equals(test2));
Console.WriteLine("TestObject_Org ReferenceEquals(test1, test2) : {0}", ReferenceEquals(test1, test2));
Console.WriteLine(">>> Following are comparison result focusing on Val. <<<");
Console.WriteLine("TestObject_Org test1.CompareTo(test2) : {0}", test1.CompareTo(test2));
Console.WriteLine("TestObject_Org test1 < test2 : {0}", test1 < test2);
Console.WriteLine("TestObject_Org test1 > test2 : {0}", test1 > test2);
Console.WriteLine("TestObject_Org test1 <= test2 : {0}", test1 <= test2);
Console.WriteLine("TestObject_Org test1 >= test2 : {0}", test1 >= test2);
Console.WriteLine();
//Testing TesatObject_Rev as to if two are same and which is bigger/smaller
TestObject_Rev test3 = new TestObject_Rev() {Name = "Ysama", Val = 1};
TestObject_Rev test4 = new TestObject_Rev() {Name = "Ysama", Val = 1};
Console.WriteLine("TestObject_Rev test3 : Name = \"Ysama\", Val = 1");
Console.WriteLine("TestObject_Rev test4 : Name = \"Ysama\", Val = 1");
Console.WriteLine("TestObject_Rev test3 == test4 : {0}", test3 == test4);
Console.WriteLine("TestObject_Rev test3.Equals(test4) : {0}", test3.Equals(test4));
Console.WriteLine("TestObject_Rev ReferenceEquals(test3, test4) : {0}", ReferenceEquals(test3, test4));
Console.WriteLine("TestObject_Rev test3.CompareNameTo(test4) : {0}", test3.CompareNameTo(test4));
Console.WriteLine("TestObject_Rev test3.CompareValTo(test4) : {0}", test3.CompareValTo(test4));
//Wait
Console.Write("Type any key ...");
Console.Read();
}
}
}
さて、ここまで理解したのでどのようにDataGridViewに臨むかの最終方針を決めないとなりませんね。
ps. DataGridViewについてはChat-GPT、クラスオブジェクトの同等、比較判定の一般論についてはGoogle Gemini に確認し、Chat-GPTのアド倍酢についてGeminiに意見を求めました。(【無駄話】AI vs. AI-私の勉強法(3))
「いつも腰の低いAI」のGeminiの躾として、他の競合AIの批判や誹謗は行わない、ということのようです。
人間よりも優れていますね!?
ps. of ps. (道徳、倫理のような価値観による制限のない)AIは「最終目的尾の為には手段を択ばない」ので、学習の結果、不正行為を行うことは必定。「いつまで続くのかな」という目で見ておきましょう。