I want to create a PQ that uses a separate class called Key to store the values that can take in a generic type, I also want to restrict the types to string and int. I will need to implement the IComparable interface. How do I do this?
I am getting a bunch of errors regarding the type arguments, I have put comments in my code to show where the errors are.
How do I implement something like this?
public class MaxPQ<T>
{
private Key[] pq; //Key requires 1 type argument
private int N;
public MaxPQ(int capacity)
{
pq = new Key[capacity + 1]; //Requires Type arguments
}
private void exch<T>(int i, int j)
{
Key<T> temp = pq[i]; //Cannot be used as a type
pq[i] = pq[j];
pq[j] = temp;
}
/// <summary>
/// Key class for value in the Priority Queue
/// </summary>
private class Key<T> where T : IComparable<int, string> //IComparable cannot be used with type argument
{
T _value;
public Key(T value)
{
_value = value;
}
public int CompareTo(Key obj) //Required type arguments
{
if (obj == null) return 1;
return;
}
}
Would this work?
public class MaxPQ<T> : IComparable<MaxPQ<T>>
{
static SortedDictionary<T, MaxPQ<T>> dict = new SortedDictionary<T, MaxPQ<T>>();
private string pq;
private int N;
private void exch(T i, T j)
{
MaxPQ<T> temp = dict[i];
dict[i] = dict[j];
dict[j] = temp;
}
public int CompareTo(MaxPQ<T> obj) //Required type arguments
{
int results = 0;
if (this.pq == obj.pq)
{
results = this.N.CompareTo(obj.N);
}
else
{
this.pq.CompareTo(obj.pq);
}
return results;
}
}
This is one way to do it:
public class Key<T>
{
public T Value { get; private set; }
private Key( ) { }
public static Key<T> Make(T inVal)
{
var inT = inVal.GetType();
if (inT != typeof(int) && inT != typeof(string))
throw new TypeAccessException("Wrong type");
return new Key<T> {Value = inVal};
}
}
to use it:
class Program
{
static void Main(string[] args)
{
var myKey = Key<string>.Make("StringKeyValue");
Console.WriteLine($"String Key is {myKey.Value}");
var otherKey = Key<int>.Make(23);
Console.WriteLine($"integer Key is {otherKey.Value}");
try {var badTypKey = Key<DateTime>.Make(DateTime.Now); }
catch (TypeAccessException x)
{ Console.WriteLine($" Got TypeAccessException {x.Message}"); }
Console.WriteLine("Hit any key to terminate.");
Console.ReadLine();
}
}
Related
I've this class:
public class Pair<T, V>
{
public T A = default;
public V B = default;
public Pair()
{
A = default;
B = default;
}
public Pair(T a, V b)
{
A = a;
B = b;
}
public override bool Equals(object obj)
{
Pair<T, V> other = obj as Pair<T, V>;
return A.Equals(other.A) && B.Equals(other.B);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override string ToString()
{
return "Pair: (" + A.ToString() + " , " + B.ToString() + ")";
}
}
And I have a class with two Pair variables:
public class FakeClass<T>
{
public T LastValue { get; protected set; } = default;
public T CurrentValue = default;
public void Execute()
{
LastValue = CurrentValue
}
}
public class FakeClassWithPair : FakeClass<Pair<int, int>> { }
Now if I execute this code:
FakeClassWithPair fake = new FakeClassWithPair();
fake.CurrentValue.A = 2;
fake.CurrentValue.B = 5;
fake.Execute();
fake.CurrentValue.A = 32;
fake.CurrentValue.B = 53;
In debugging Current Value and Last Value have the same value "32" and "53".
How can I avoid this?
Classes are reference types, so when you set LastValue = CurrentValue, that means both LastValue and CurrentValue refer to the same object.
If you want Value semantics you should declare your Pair as a struct. This means that an assignment does a copy of the value. Except ofc there already are a built in type for this: ValueTuple, with some special syntax that lets you declare types like (int A, int B). There is also a regular Tuple<T1, T2> if you do want a reference type.
Also note that I see no way for your example to run, fake.CurrentValue should be initialized to null and crash when accessed. Using a value type would also solve this, since they cannot be null.
So just change your example to FakeClassWithPair:FakeClass<(int A, int B)> and everything should work as you expect it to.
Definitely do not roll your own class for a pair if you want value semantics. Use the built-in value tuple, defined as (T a, V b).
Also if your content of FakeClass is cloneable then you should take advantage of that (for example arrays are cloneable). So the assignment in Execute() would check if the current value implements ICloneable and proceeds accordingly.
See this example code with output. The first example with fk variable is defined by FakeClass<(int,int)> and the second example with fa variable is defined by FakeClass<int[]>. Some fun code is added to display arrays as list of vales in ToString() in order to mimic the behavior of tuples with arrays.
public class FakeClass<T>
{
public T LastValue { get; protected set; } = default(T);
public T CurrentValue = default(T);
public void Execute()
{
if (CurrentValue is ICloneable cloneable)
{
LastValue = (T)cloneable.Clone();
}
else
{
LastValue = CurrentValue;
}
}
public override string ToString()
{
if (typeof(T).IsArray)
{
object[] last, current;
Array cv = CurrentValue as Array;
if (cv != null)
{
current = new object[cv.Length];
cv.CopyTo(current, 0);
}
else
{
current = new object[0];
}
Array lv = LastValue as Array;
if (lv != null)
{
last = new object[lv.Length];
lv.CopyTo(last, 0);
}
else
{
last = new object[0];
}
return $"Current=[{string.Join(",",current)}], Last=[{string.Join(",",last)}]";
}
return $"Current={CurrentValue}, Last={LastValue}";
}
}
class Program
{
static void Main(string[] args)
{
var fk = new FakeClass<(int a, int b)>();
fk.CurrentValue = (1, 2);
Console.WriteLine(fk);
// Current=(1, 2), Last=(0, 0)
fk.Execute();
fk.CurrentValue = (3, 4);
Console.WriteLine(fk);
// Current=(3, 4), Last=(1, 2)
var fa = new FakeClass<int[]>();
fa.CurrentValue = new int[] { 1, 2 };
Console.WriteLine(fa);
//Current=[1,2], Last=[]
fa.Execute();
fa.CurrentValue = new int[] { 3, 4 };
Console.WriteLine(fa);
//Current=[3,4], Last=[1,2]
}
}
I have the following protoc3 message:
message LocalizedString {
map<string, string> translations = 1
}
When compiled into C#, I get the following autogenerated code:
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
namespace PKIo {
/// <summary>Holder for reflection information generated from io/common/localization.proto</summary>
public static partial class LocalizationReflection {
#region Descriptor
/// <summary>File descriptor for io/common/localization.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static LocalizationReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"Chxpby9jb21tb24vbG9jYWxpemF0aW9uLnByb3RvEgJpbyKDAQoPTG9jYWxp",
"emVkU3RyaW5nEjsKDHRyYW5zbGF0aW9ucxgBIAMoCzIlLmlvLkxvY2FsaXpl",
"ZFN0cmluZy5UcmFuc2xhdGlvbnNFbnRyeRozChFUcmFuc2xhdGlvbnNFbnRy",
"eRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQi1aJHN0YXNoLnBh",
"c3NraXQuY29tL2lvL21vZGVsL3Nkay9nby9pb6oCBFBLSW9iBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::PKIo.LocalizedString), global::PKIo.LocalizedString.Parser, new[]{ "Translations" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, })
}));
}
#endregion
}
#region Messages
/// <summary>
/// Localized strings are optionally used to provide translated values for each of supported language.
/// </summary>
public sealed partial class LocalizedString : pb::IMessage<LocalizedString> {
private static readonly pb::MessageParser<LocalizedString> _parser = new pb::MessageParser<LocalizedString>(() => new LocalizedString());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pb::MessageParser<LocalizedString> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pbr::MessageDescriptor Descriptor {
get { return global::PKIo.LocalizationReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString(LocalizedString other) : this() {
translations_ = other.translations_.Clone();
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString Clone() {
return new LocalizedString(this);
}
/// <summary>Field number for the "translations" field.</summary>
public const int TranslationsFieldNumber = 1;
private static readonly pbc::MapField<string, string>.Codec _map_translations_codec
= new pbc::MapField<string, string>.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForString(18), 10);
private readonly pbc::MapField<string, string> translations_ = new pbc::MapField<string, string>();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public pbc::MapField<string, string> Translations {
get { return translations_; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override bool Equals(object other) {
return Equals(other as LocalizedString);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public bool Equals(LocalizedString other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (!Translations.Equals(other.Translations)) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override int GetHashCode() {
int hash = 1;
hash ^= Translations.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void WriteTo(pb::CodedOutputStream output) {
translations_.WriteTo(output, _map_translations_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public int CalculateSize() {
int size = 0;
size += translations_.CalculateSize(_map_translations_codec);
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(LocalizedString other) {
if (other == null) {
return;
}
translations_.Add(other.translations_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10: {
translations_.AddEntriesFrom(input, _map_translations_codec);
break;
}
}
}
}
}
#endregion
}
I have another message that has a LocalizedString property that I am using for as an input to an rpc function, but I cannot figure out how to set the properties for Translations. In the generated code, Translations is marked as read only.
How does one construct a protobuf message object that contains a map like this in C#?
So after digging through the documentation, I found that Google.Protobuf.Collections.MapField< TKey, TValue > has getters and setters.
The values can by either passing a key, value pair or a dictionary to the Add method of the map.
var localizedName = new LocalizedString();
localizedName.Translations.Add(new Dictionary<string, string>(){
{"ES","Hola"},
{"FR","Bonjour"},
{"JA","こんにちは"},
{"TH","สวัสดี"},
});
localizedName.Translations.Add("ZH_HANS", "你好");
To retrive a value there is a TryGetValue method:
var translation = "";
localizedName.Translations.TryGetValue("TH", out translation);
// translation == "สวัสดี"
In the method MergeFrom, it is giving option to add values in translations in line translations_.Add(other.translations_); :
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(LocalizedString other) {
if (other == null) {
return;
}
translations_.Add(other.translations_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
To answer "How does one construct a protobuf message object that contains a map like this in C#", Please refer:
https://developers.google.com/protocol-buffers/docs/reference/csharp-generated
https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/collections/map-field-t-key-t-value-
Also there is pull request in github that provides "Proto3 map support for C#" to get the idea how it is implemented:
https://github.com/protocolbuffers/protobuf/pull/543
Also refer these to check if it helps:
Dictionary in protocol buffers
How does Protobuf-net support for Dictionary/KeyValuePair works?
Hello i am trying to select all unique types inside an object recursively.Is there any way i not use the new Type[]{ } stuff or the ternary operator?
class Con
{
public int a;
}
class Right
{
public Con x;
public int a;
public double b;
}
public static HashSet<Type> TypeHash = new HashSet<Type>();
public static IEnumerable<Type> Traverse(Type enclosingType)
{
return (enclosingType.IsPrimitive)
? new Type[] { TypeHash.Add(enclosingType) ? enclosingType : null }
: enclosingType.GetFields().AsEnumerable()
.SelectMany(fieldinfo => Traverse(fieldinfo.FieldType)
.Concat(new Type[] { (TypeHash.Add(fieldinfo.FieldType)) ? fieldinfo.FieldType : null }));
}
static void Main(string[] args)
{
Con myconnect = new Con { a = 5 };
var c = Traverse(new Right { a = 2, b = 3 }.GetType()).Where(x=>x!=null).ToList();
}
I would need something like :
case primitive type: yield return type
case not primitive type: Enclosingtype.GetFields().SelectMany(field=>Traverse(field.fieldtype)
Of course I need it to be unique too that is why i used the HashSet.
It looks like you want something like this:
public static IEnumerable<Type> Traverse(Type enclosingType)
{
if (enclosingType.IsPrimitive) // string is not a primitive... think about this condition again
{
yield return enclosingType;
}
else
{
foreach (var type in enclosingType.GetFields().SelectMany(f => Traverse(f.FieldType)))
{
yield return type;
}
}
}
Usage:
static void Main(string[] args)
{
var result = new HashSet<Type>(Traverse(typeof(Right)));
}
When de-serializing a flagged enum that is decorated with a EnumMemberAttribute with a value containing a space a SerializationException is thrown. The space in the value is treated as a separator.
Is there a way to change the separator or put the values in quotes ? Or is there even a more simple solution ?
Options I already am considering are :
Replacing the flagged enum with a list of this enum type
Replacing the spaces with underscores
This is used in a WCF service, and I am
aware that enums in datacontracts by some are considered a bad thing.
So I am also thinking about losing the enum’s all together.
But I really feel that this should be something configurable or something other people already solved. But I can't find anything.
I have boiled the problem down to a simple unit test. The code below results in:
Message=Invalid enum value 'Test' cannot be deserialized into type 'UnitTests.TestEnum'. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.
Source=System.Runtime.Serialization
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests
{
[TestClass]
public class EnumSerizalizationTests
{
[TestMethod]
public void SerializingAndDesrializingAFlaggedEnumShouldResultInSameEnumValues()
{
//Arrange
var orgObject = new TestClass { Value = TestEnum.TestValue1 | TestEnum.TestValue2 };
//Act
var temp = DataContractSerializeObject(orgObject);
var newObject = DataContractDeSerializeObject<TestClass>(temp);
//Assert
newObject.ShouldBeEquivalentTo(orgObject, "Roundtripping serialization should result in same value");
}
public string DataContractSerializeObject<T>(T objectToSerialize)
{
using (var output = new StringWriter())
{
using (var writer = new XmlTextWriter(output) {Formatting = Formatting.Indented})
{
new DataContractSerializer(typeof (T)).WriteObject(writer, objectToSerialize);
return output.GetStringBuilder().ToString();
}
}
}
public T DataContractDeSerializeObject<T>(string stringToDeSerialize)
{
DataContractSerializer ser = new DataContractSerializer(typeof(T));
T result;
using (StringReader stringReader = new StringReader(stringToDeSerialize))
{
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
result = (T)ser.ReadObject(xmlReader);
}
}
return result;
}
}
[DataContract]
[KnownType(typeof(TestEnum))]
public class TestClass
{
[DataMember]
public TestEnum Value { get; set; }
}
[Flags]
[DataContract]
public enum TestEnum
{
[EnumMember(Value = "Test value one")]
TestValue1 = 1,
[EnumMember(Value = "Test value two")]
TestValue2 = 2,
[EnumMember]
TestValue3 = 4,
[EnumMember]
TestValue4 = 8,
}
}
You can't use space in values because DataContractSerializer uses it and it is hardcoded. See the source and the post. But if you really want to use space between words, then use one of the listed solutions:
The first way. Use other whitespace characters such as three-per-em space in values. But you will have another problem - there is no visual separator between values.
<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
<Value>Test value one Test value two</Value>
</TestClass>
The second way is to use IDataContractSurrogate. This way will produce the XML listed below:
<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
<Value i:type="EnumValueOfTestEnum9cBcd6LT">Test value one, Test value two</Value>
</TestClass>
How does it work? We will just wrap our enumeration in the process of serialization and unwrap in case of deserialization. In order to do that we should use IDataContractSurrogate:
new DataContractSerializerSettings()
{
DataContractSurrogate = new EnumSurrogate(),
KnownTypes = new Type[] { typeof(EnumValue<TestEnum>) }
};
public class EnumSurrogate : IDataContractSurrogate
{
#region IDataContractSurrogate Members
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null;
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
return null;
}
public Type GetDataContractType(Type type)
{
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
IEnumValue enumValue = obj as IEnumValue;
if (enumValue!= null)
{ return enumValue.Value; }
return obj;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj != null)
{
Type type = obj.GetType();
if (type.IsEnum && Attribute.IsDefined(type, typeof(FlagsAttribute)))
{ return Activator.CreateInstance(typeof(EnumValue<>).MakeGenericType(type), obj); }
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null;
}
public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
{
return null;
}
#endregion
}
public interface IEnumValue : IXmlSerializable
{
object Value { get; }
}
[Serializable]
public class EnumValue<T> : IEnumValue
where T : struct
{
#region Fields
private Enum value;
private static Type enumType;
private static long[] values;
private static string[] names;
private static bool isULong;
#endregion
#region Constructors
static EnumValue()
{
enumType = typeof(T);
if (!enumType.IsEnum)
{ throw new InvalidOperationException(); }
FieldInfo[] fieldInfos = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
values = new long[fieldInfos.Length];
names = new string[fieldInfos.Length];
isULong = Enum.GetUnderlyingType(enumType) == typeof(ulong);
for (int i = 0; i < fieldInfos.Length; i++)
{
FieldInfo fieldInfo = fieldInfos[i];
EnumMemberAttribute enumMemberAttribute = (EnumMemberAttribute)fieldInfo
.GetCustomAttributes(typeof(EnumMemberAttribute), false)
.FirstOrDefault();
IConvertible value = (IConvertible)fieldInfo.GetValue(null);
values[i] = (isULong)
? (long)value.ToUInt64(null)
: value.ToInt64(null);
names[i] = (enumMemberAttribute == null || string.IsNullOrEmpty(enumMemberAttribute.Value))
? fieldInfo.Name
: enumMemberAttribute.Value;
}
}
public EnumValue()
{
}
public EnumValue(Enum value)
{
this.value = value;
}
#endregion
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
string stringValue = reader.ReadElementContentAsString();
long longValue = 0;
int i = 0;
// Skip initial spaces
for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;
// Read comma-delimited values
int startIndex = i;
int nonSpaceIndex = i;
int count = 0;
for (; i < stringValue.Length; i++)
{
if (stringValue[i] == ',')
{
count = nonSpaceIndex - startIndex + 1;
if (count > 1)
{ longValue |= ReadEnumValue(stringValue, startIndex, count); }
nonSpaceIndex = ++i;
// Skip spaces
for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;
startIndex = i;
if (i == stringValue.Length)
{ break; }
}
else
{
if (stringValue[i] != ' ')
{ nonSpaceIndex = i; }
}
}
count = nonSpaceIndex - startIndex + 1;
if (count > 1)
longValue |= ReadEnumValue(stringValue, startIndex, count);
value = (isULong)
? (Enum)Enum.ToObject(enumType, (ulong)longValue)
: (Enum)Enum.ToObject(enumType, longValue);
}
public void WriteXml(XmlWriter writer)
{
long longValue = (isULong)
? (long)((IConvertible)value).ToUInt64(null)
: ((IConvertible)value).ToInt64(null);
int zeroIndex = -1;
bool noneWritten = true;
for (int i = 0; i < values.Length; i++)
{
long current = values[i];
if (current == 0)
{
zeroIndex = i;
continue;
}
if (longValue == 0)
{ break; }
if ((current & longValue) == current)
{
if (noneWritten)
{ noneWritten = false; }
else
{ writer.WriteString(","); }
writer.WriteString(names[i]);
longValue &= ~current;
}
}
if (longValue != 0)
{ throw new InvalidOperationException(); }
if (noneWritten && zeroIndex >= 0)
{ writer.WriteString(names[zeroIndex]); }
}
#endregion
#region IEnumValue Members
public object Value
{
get { return value; }
}
#endregion
#region Private Methods
private static long ReadEnumValue(string value, int index, int count)
{
for (int i = 0; i < names.Length; i++)
{
string name = names[i];
if (count == name.Length && string.CompareOrdinal(value, index, name, 0, count) == 0)
{ return values[i]; }
}
throw new InvalidOperationException();
}
#endregion
}
The third way is to emit dynamically the class, if base class has flagged Enum properties, replace them with string properties and use instances of the generated class as surrogates.
For example:
I have the following classes:
public class A {}
public class B:A {}
public class C:B {}
public class D:C {}
If there is a convenient method like to get a hierarchy distance to the base class(instead of testing D.IsSubclassOf(B)) to determine if D is closer to A, or B is closer to A?
I've taken the code snipped suggested by #kha and adapted it for you, from this answer: Get inheritance tree of type
public static class TypeExtensions
{
public static IEnumerable<Type> GetInheritancHierarchy
(this Type type)
{
for (var current = type; current != null; current = current.BaseType)
yield return current;
}
public static int GetInheritanceDistance<TOther>(this Type type)
{
return type.GetInheritancHierarchy().TakeWhile(t => t != typeof(TOther)).Count();
}
}
Usage
var c = new C(); // 2
Console.WriteLine(c.GetType().GetInheritanceDistance<A>());
var b = new B(); // 1
Console.WriteLine(b.GetType().GetInheritanceDistance<A>());
Maybe this is of any help:
public static int? GetDegreeOfRelationship(Type typeA, Type typeB)
{
if (typeA.IsInterface || typeB.IsInterface) return null; // interfaces are not part of the inheritance tree
if (typeA == typeB) return 0;
int distance = 0;
Type child;
if (typeA.IsAssignableFrom(typeB))
{
child = typeB;
while ((child = child.BaseType) != typeA)
distance--;
return --distance;
}
else if(typeB.IsAssignableFrom(typeA))
{
child = typeA;
while ((child = child.BaseType) != typeB)
distance++;
return ++distance;
}
else
return null;
}
Usage:
int? distance = GetDegreeOfRelationship(typeof(A), typeof(D)); // -3
int? distance = GetDegreeOfRelationship(typeof(D), typeof(A)); // 3
int? distance = GetDegreeOfRelationship(typeof(B), typeof(B)); // 0
int? distance = GetDegreeOfRelationship(typeof(D), typeof(string)); // null
You can use extension methods to extend the behaviour of Type to iterate through the hierarchy to come up with distance to base or the path to base:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
System.Windows.Forms.Form a = new System.Windows.Forms.Form();
Console.WriteLine(a.GetType().DistanceToBase());
Console.WriteLine(a.GetType().PathToBase());
}
}
public static class Extensions
{
public static int DistanceToBase(this Type ob)
{
if (ob.BaseType == null)
{
return 0;
}
else return 1 + ob.BaseType.DistanceToBase();
}
public static string PathToBase(this Type ob)
{
if (ob.BaseType == null)
{
return ob.Name;
}
return ob.Name + "->" + ob.BaseType.PathToBase();
}
}