151. Delegate CreateDelegate

ראינו בטיפים הקודמים כיצד ניתן להפעיל מתודה באמצעות הMethodInfo שמייצג אותה. (טיפ מספר 148)

כמו שאמרתי אז, הרצת מתודה באמצעות Invoke היא יותר איטית, ואנחנו עשויים להרגיש בזה כאשר אנחנו מריצים את המתודה פעמים רבות (למשל, בתוך לולאה).

אפשר לבצע אופטימיזציה באמצעות delegateים:

במידה ואנחנו יודעים את החתימה של הפונקציה (כלומר מה הפרמטרים וערך ההחזר שלה), אנחנו יכולים ליצור delegate שיקרא לפונקציה שלנו. את זה נוכל לעשות באמצעות הפונקציה Delegate.CreateDelegate.

Overload אחד של הפונקציה הזאת מאפשר לנו ליצור delegate כזה עפ”י השם של הפונקציה בלבד:

במקרה של פונקציה סטטית:

1
2
3
4
5
6
7
8
9
10
11
12
Func<string, object[], string> format =(Func<string, object[], string>)
Delegate.CreateDelegate
(typeof(Func<string, object[], string>),typeof(string), "Format");
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(format("{0} in hex is {1}",
new object[] {i, i.ToString("X")}));
//Console.WriteLine(string.Format("{0} in hex is {1}",
// new object[] {i, i.ToString("X")}));
}

שימו לב לסינטקס – אנחנו מעבירים את החתימה של המתודה שאנחנו מעוניינים לקרוא לה בתור פרמטר ראשון, את הטיפוס (הType) לו שייכת המתודה בתור פרמטר שני ואת השם של המתודה בתור פרמטר שלישי. לבסוף, אנחנו צריכים לעשות הסבה לטיפוס של הdelegate שלנו – זה בעצם סוד הקסם של האופטימיזציה.

אם אנחנו רוצים להיות יותר מדויקים, קיים גם overload שמקבל את הMethodInfo במקום את השם של הפונקציה (ואז לא צריך את הType):

1
2
3
4
5
6
7
MethodInfo formatInfo =
typeof (string).GetMethod("Format", new Type[] {typeof(string), typeof (object[])});
Func<string, object[], string> format =
(Func<string, object[], string>)
Delegate.CreateDelegate
(typeof(Func<string, object[], string>), formatInfo);

אם אנחנו רוצים לקרוא לפונקציה של instance, ניתן לעשות זאת כך:

1
2
3
4
5
6
7
8
9
10
11
12
MethodInfo replaceInfo =
typeof (string).GetMethod("Replace", new Type[] {typeof (string), typeof (string)});
Func<string, string, string, string> replace =
(Func<string, string, string, string>)
Delegate.CreateDelegate(typeof (Func<string, string,string, string>), replaceInfo);
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(replace(i.ToString(), "0", ""));
//Console.WriteLine(i.ToString().Replace("0", ""));
}

(הקוד מסיר אפסים מההצגה העשרונית של מספר)

קצת מפחיד, אבל מה שקורה כאן זה שאנחנו יוצרים delegate שמקבל בתור הפרמטר הראשון את הinstance שעליו אנחנו רוצים להריץ את המתודה, ושאר הפרמטרים הם הפרמטרים של המתודה. (כמו בטיפ 148 בקריאה לInvoke).


בנוסף, אם אנחנו כל הזמן ניגשים לאותו Instance, ניתן ליצור delegate שיפעל תמיד על הinstance ההוא. למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MethodInfo containsInfo =
typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
object theNumbers = "The numbers are 4,8,15,16,23,42";
Func<string, bool> contains =
(Func<string, bool>)
Delegate.CreateDelegate(typeof (Func<string, bool>),
theNumbers,
containsInfo);
for (int i = 0; i < 10000; i++)
{
if (contains(i.ToString()))
//if (theNumbers.Contains(i.ToString()))
{
Console.WriteLine(i);
}
}

למה זה יותר מהיר? מה שקורה זה שהDynamic Binding קורה רק פעם אחת. מה ז"א?

כשאנחנו קוראים לInvoke של MethodInfo מפוענחת איך תתבצע הקריאה – כלומר לאיזו חתימה אנחנו מצפים לקרוא, ואיך לשלוח את הפרמטרים (הרי הטיפוס שאנחנו שולחים לפונקציה לא חייב להיות זהה לטיפוס שהמתודה מצפה לקבל – הוא יכול גם לרשת ממנו וכו’) ואיך להחזיר את ערך ההחזר.

מיפוי זה מבזבז את רוב הזמן בקריאה לפונקציה.

במקום זאת, כל המיפוי הזה מתבצע פעם יחידה, בה אנחנו יוצרים את הdelegate. בעצם כל המיפוי הזה נשמר בתוך הdelegate, וכך אנחנו חוסכים ממנו להתבצע כל פעם.

חג פסח שמח וחופש נעים!

שתף

150. Calling generic methods with unknown generic type

אחד הדברים החזקים שאפשר לעשות עם MethodInfo הוא לקרוא לפונקציות גנריות, כשאנחנו לא יודעים מהו הטיפוס הגנרי.

למשל, נניח שכתבנו ממשק גנרי כזה:

1
2
3
4
public interface ICloneable<T>
{
T Clone();
}

עכשיו נתון לנו אוסף של אובייקטים. מתוכו אנחנו מעוניינים לחלץ את כל האובייקטים מטיפוס כזה, כאשר לכל אחד כזה יכול להיות פרמטר גנרי אחר, ומתוכו ליצור אוסף חדש של טיפוסים חדשים ע"י קריאה למתודה Clone.

היינו מנסים לכתוב קוד כזה:

1
2
3
4
5
6
7
8
9
10
ICollection<object> clonedObjects = new List<object>();
foreach (object current in enumerable)
{
if (current is ICloneable<>) // Compile error
{
ICloneable<> currentClonable = (ICloneable<>)current; // Compile error
clonedObjects.Add(currentClonable.Clone());
}
}

נראה מבטיח, אלא שזה לא יתקמפל…

בעיה זו היא די נדירה כאשר הטיפוסים והממשקים מעוצבים נכון. למרבה הצער, לא תמיד הכל מעוצב נכון.

נוכל לפתור בעיה זו באמצעות שימוש בReflection וקריאה לMethodInfo המתאים:

ניצור פונקציה כזאת:

1
2
3
4
public static T Clone<T>(ICloneable<T> source)
{
return source.Clone();
}

ונקרא לה בReflection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ICollection<object> clonedObjects = new List<object>();
foreach (object current in enumerable)
{
IEnumerable<Type> genericClonableTypes =
from type in current.GetType().GetInterfaces()
where type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(ICloneable<>)
select type;
if (genericClonableTypes.Any())
{
Type genericArgument =
genericClonableTypes.First().GetGenericArguments()[0];
MethodInfo cloneMethod =
typeof(SuperMethods).GetMethod("Clone").MakeGenericMethod(genericArgument);
object cloned =
cloneMethod.Invoke(null, new object[] {current});
clonedObjects.Add(cloned);
}
}

טוב, יש פה די הרבה קוד, אבל מה שקורה כאן זה שאנחנו בודקים עבור כל טיפוס אם הוא מממש ICloneable<> כלשהו, ובמידה וכן, אנחנו קוראים לפונקציה Clone (זו שכתובה למעלה) עם הפרמטר הגנרי של אותו ICloneable<>. כפי שכתבתי קודם, אם הטיפוסים מעוצבים מספיק טוב, לא כל כך נתקלים בבעיה זו.

למשל, אם היינו דואגים שיהיה גם ממשק לא גנרי, למשל:

1
2
3
4
public interface ICloneable
{
object Clone();
}

אם היינו דואגים שכל מי שיממש את הטיפוס ICloneable<T> יממש גם ICloneable, היינו יכולים פשוט לעשות הסבה לICloneable ולהשתמש בו.

אבל למה הכוונה לדאוג? אפשר לעשות שICloneable<T> יממש ICloneable, אבל אז צריך לממש את אותה מתודה פעמיים. (ומי מבטיח לנו שמממשים אותה נכון?)

אפשר לסמוך על המממשים שיממשו גם ICloneable וגם ICloneable<T>, אבל מה אם הם לא יממשו?

בכל מקרה, ממשק לא גנרי יכול לעזור לפתור את הבעיה הזו, אבל גם לו יש מספר חסרונות, למרות שמומלץ להשתמש בו.

אנחנו נראה בעתיד עוד דרכים לפתור את הבעיה הזו. (ראו גם טיפ מספר 231)

סופ"ש גנרי משתקף לטובה

שתף

149. MakeGenericMethod

ראינו איך אפשר להריץ מתודות על פי הMethodInfo שלהן.

מה אם המתודה שלנו גנרית?

למשל:

1
2
3
4
5
6
7
public static class SuperMethods
{
public static IEnumerable<T> ToArray<T>(T item)
{
return new T[] {item};
}
}

(ראו גם טיפים 26-27)

נוכל לנסות לעשות משהו כזה, כמו שעשינו עד עכשיו:

1
2
3
4
5
MethodInfo toArray =
typeof (SuperMethods).GetMethod("ToArray");
object array =
toArray.Invoke(null, new object[] {"The string"});

אלא שנקבל Exception כזה:

InvalidOperationException: Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.

הסיבה היא די ברורה – המתודה לא יודעת מהו הפרמטר הגנרי שאנחנו רוצים לשלוח לפונקציה. כדי לפתור זאת, נוכל להשתמש במתודה דומה למתודה MakeGenericType (טיפ מספר 140). שמה הוא MakeGenericMethod. למתודה זו אנחנו מעבירים את הפרמטרים הגנריים שאנחנו מעוניינים לשלוח לפונקציה, והיא מחזירה לנו MethodInfo המתאר את הפונקציה עם הפרמטר הגנרי המוזרק:

1
2
3
4
5
6
7
8
9
MethodInfo toArray =
typeof (SuperMethods).GetMethod("ToArray");
MethodInfo stringToArray =
toArray.MakeGenericMethod(typeof (string));
object array =
stringToArray.Invoke(null, new object[] { "The string"}); // new []{ "The string" }
// Like object array = SuperMethods.ToArray<string>("The string");

הדבר מאוד חזק, מאחר והוא מאפשר לנו לקרוא לפונקציות גנריות עם טיפוסים שאנחנו מכירים רק בזמן ריצה.

המשך יום גנרי משתקף לטובה

שתף

148. Invoking a MethodInfo

בהמשך לשבוע המשתקף לטובה,

ראינו איך אפשר להשיג MethodInfo.

באופן די צפוי, אחד הדברים המגניבים שאפשר לעשות עם MethodInfo שכזה הוא להפעיל את הפונקציה שהוא מייצג.

איך זה מתבצע? בעזרת הפונקציה Invoke. פונקציה זו מאפשרת לנו לקרוא לפונקציה שמייצג הMethodInfo עם פרמטרים מתאימים.

אם הפונקציה שלנו סטטית, השימוש די פשוט:

1
2
3
4
5
6
MethodInfo parse =
typeof (int).GetMethod("Parse", new[] {typeof(string)});
object result = parse.Invoke(null, new object[] {"42"});// 42
// Like object result = int.Parse("42");

אם הפונקציה שלנו לא סטטית, השימוש גם פשוט, אלא שהפעם בפרמטר הראשון אנחנו צריכים לציין את הinstance שעליו אנחנו מפעילים את הפונקציה:

1
2
3
4
5
6
MethodInfo substring =
typeof (string).GetMethod("Substring", new[] {typeof(int)});
object result = substring.Invoke("Pink Floyd", new object[] {1}); // ink Floyd
// Like object result = "Pink Floyd".Substring(1);

מגניב, לא?


הדבר הזה די מגניב, ודי שימושי. אלא שבדומה לActivator.CreateInstance (ראו גם טיפ מספר 139), קריאה כזו למתודה היא לא כל כך יעילה. למען האמת זה עולה לנו בביצועים בסביבות כמה מיקרו-שניות. זה נשמע לא הרבה, אבל אם נקרא למתודה בצורה כזאת הרבה פעמים מתוך לולאה, אז נרגיש ירידה משמעותית בביצועים.

יש לבעיה זו פתרון ואותו נראה בהמשך…

המשך יום משתקף טוב

שתף

147. Obtaining a MethodInfo

הכרנו פעם שעברה קצת את MethodInfo.

יש מספר דרכים להשיג MethodInfo. הדרך הקלה ביותר היא להשתמש בפונקציה GetMethods של Type.

כפי שראינו פעם שעברה, פונקציה זו מחזירה לנו את כל הMethodInfoים הpublic שיש לTypeמסוים. (גם סטטיות וגם של instance)

אבל אם לא מעניינות אותנו כל המתודות? נוכל להשתמש בoverload של הפונקציה GetMethods שמקבל BindingFlags. זהו enum שבעזרתו אנחנו מסוגלים לציין את המתודות שאנחנו מעוניינים לחפש:

למשל:

1
2
3
MethodInfo[] publicNonStaticMethods =
typeof (int).GetMethods(BindingFlags.Public |
BindingFlags.Instance);

יחזיר לנו את כל המתודות שהן public ושל instance של הטיפוס int.

1
2
3
4
MethodInfo[] nonPublicMethods =
typeof (int).GetMethods(BindingFlags.NonPublic |
BindingFlags.Static |
BindingFlags.Instance);

יחזיר לנו את כל המתודות שהן לא public (כן, אפשר לעשות את זה!)

1
2
MethodInfo[] emptyMethods =
typeof (int).GetMethods(BindingFlags.Public);

לא יחזיר לנו כלום, כי לא ציינו אם אנחנו מעוניינים בפונקציות סטטיות או של instance.

(ראו גם טיפ מספר 22 אם אתם לא מכירים Enum flags)


דרך נוספת להשיג MethodInfo היא באמצעות הפונקציה GetMethod:

1
MethodInfo contains = typeof (string).GetMethod("Contains");

זו פונקציה המחזירה לנו MethodInfo לפי שם של פונקציה. לפעמים, לא מספיק לדעת לאיזה פונקציה התכוונו רק לפי השם שלה, אלא צריך לדעת פרטים נוספים, כגון הפרמטרים שלה.

1
2
3
MethodInfo indexOf =
typeof (string).GetMethod("IndexOf");
// AmbiguousMatchException: Ambiguous match found.

לכן יש Overload שמאפשר לנו לציין את סוג הפרמטרים שאנחנו מצפים שיהיו לפונקציה:

1
MethodInfo indexOf = typeof (string).GetMethod("IndexOf", new[] {typeof(string)});

יש עוד כמה overloadים שמאפשרים לנו להיות יותר ספציפיים ולהגיד גם את הBindingFlags שלחפש בעזרתם את המתודה וכו’.

שיהיה המשך יום משתקף טוב

שתף

146. MethodInfo

עד כה התעסקנו בעיקר בשימוש במחלקה Type ולכן בעיקר ביחסים בין מחלקות/ממשקים.

דבר נוסף שאפשר לעשות באמצעות Reflection הוא לגלות מידע על דברים שמרכיבים את הType כמו הMemberים שמרכיבים Type.

למשל, נניח ויש לנו טיפוס, אנחנו מסוגלים למצוא את כל המתודות שהוא מכיל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Type intType = typeof (int);
foreach (MethodInfo methodInfo in intType.GetMethods())
{
Console.WriteLine(methodInfo);
}
// Int32 CompareTo(System.Object)
// Int32 CompareTo(Int32)
// Boolean Equals(System.Object)
// Boolean Equals(Int32)
// Int32 GetHashCode()
// System.String ToString()
// System.String ToString(System.String)
// System.String ToString(System.IFormatProvider)
// System.String ToString(System.String, System.IFormatProvider)
// Int32 Parse(System.String)
// Int32 Parse(System.String, System.Globalization.NumberStyles)
// Int32 Parse(System.String, System.IFormatProvider)
// Int32 Parse(System.String, System.Globalization.NumberStyles, System.IFormatProvider)
// Boolean TryParse(System.String, Int32 ByRef)
// Boolean TryParse(System.String, System.Globalization.NumberStyles, System.IFormatProvider, Int32 ByRef)
// System.TypeCode GetTypeCode()
// System.Type GetType()

שימו לב שבעצם רשמנו את כל המתודות שיש לint. מגניב ביותר!


אז מהו בעצם אותו MethodInfo המסתורי?

כפי שאפשר לנחש, זהו אובייקט המייצג את המתודה שלנו – או במילים יותר יפות – הMetaData של המתודה שלנו.

מה אפשר לעשות איתו? דברים די הגיוניים, למשל לגלות את הפרמטרים שלו:

1
2
3
4
5
6
7
8
9
10
MethodInfo daysInMonth =
typeof (DateTime).GetMethod("DaysInMonth");
foreach (ParameterInfo parameter indaysInMonth.GetParameters())
{
Console.WriteLine(parameter);
}
// Int32 year
// Int32 month

או לגלות את ערך ההחזר שלו:

1
2
Console.WriteLine(daysInMonth.ReturnType);
// System.Int32

אפשר לבדוק גם האם המתודה סטטית:

1
Console.WriteLine(daysInMonth.IsStatic); // True

או לבדוק האם היא public וכו’:

1
2
Console.WriteLine(daysInMonth.IsPublic); // True
Console.WriteLine(daysInMonth.IsPrivate); // False

מה שמעניין לבדוק זה מה קורה כשאנחנו יוצרים מתודה שמקבלת אובייקטים בout או בref.

למשל:

1
2
3
4
public static Person SwapPeople(ref Person first, ref Person second)
{
// ...
}

אם נריץ את אותו קוד:

1
2
3
4
5
6
7
8
9
MethodInfo swapPeople =
typeof (Person).GetMethod("SwapPeople");
foreach (ParameterInfo parameterInfo in swapPeople.GetParameters())
{
Console.WriteLine(parameterInfo);
}
// MyNamespace.Person& first
// MyNamespace.Person& second

שימו לב ל&. מזכיר קצת את ימי C++

שיהיה שבוע השתקפויות טוב

שתף

145. Type and Array

ראינו איך אפשר להזריק לטיפוס גנרי את הפרמטרים הגנריים שלו.

נניח שיש לנו Type ואנחנו מעוניינים ליצור ממנו Type אחר המייצג מערך של הType הראשון.

הדרך לעשות זאת היא באמצעות הפונקציה MakeArrayType:

1
2
Type stringType = typeof(string);
Type stringArrayType = stringType.MakeArrayType(); // typeof(string[])

אם אנחנו מעוניינים במערך ממימד גדול יותר, קיים overload שמקבל את המימד:

1
Type string3DArrayType = stringType.MakeArrayType(3); // typeof(string[,,])

הערה: אם אנחנו מעוניינים ליצור מערך בגודל דינמי בזמן ריצה, נוכל לעשות זאת באמצעות הפונקציה הסטטית Array.CreateInstance:

למשל:

1
2
object stringArray = Array.CreateInstance(typeof (string), 2, 3, 5, 8);
// Like object stringArray = new string[2, 3, 5, 8];

שיהיה סופ"ש טיפוסי טוב!

שתף

144. IsInstanceOfType

לפעמים אנחנו מעוניינים לבדוק האם טיפוס מסוים הוא instance של Type מסוים.

הדרך הראשונה שעולה לראש היא להשוות את GetType של הinstance עם הType הנתון.

זה, כמובן, לא יעבוד כאשר הטיפוסים לא שווים (ראו גם טיפ מספר 137)

דרך שנייה היא לתקן את הדרך הראשונה: נשתמש במקום בהשוואה רגילה, בIsAssignableFrom:

1
2
3
4
5
6
7
Type type;
object instance;
if (type.IsAssignableFrom(instance.GetType()))
{
// Like (instance is type)
}

זו דרך שתעבוד ברוב המקרים, אבל לא תעבוד אם instance הוא null. (נקבל NullReferenceException)

אפשר כמובן לתקן את התנאי ולבדוק אם הinstance הוא null.

אבל במקום זאת, יש פונקציה שעושה את זה בשבילנו:

1
2
3
4
if (type.IsInstanceOfType(instance))
{
// Like (instance is type)
}

המשך יום טיפוסי

שתף

143. IsAssignableFromGenericInterface extension method

בהמשך לטיפ של אתמול,

לפעמים אנחנו מעוניינים לדעת האם טיפוס מסוים מממש ממשק גנרי, אבל לא אכפת לנו מהו הפרמטר הגנרי.

למרבה הצער, בדוגמאות הבאות, הפונקציה IsAssignableFrom תכשל:

דוגמה ראשונה:

פשוט ננסה להשתמש בIsAssignableFrom על unbound generic type…

יש לנו מחלקה כזאת

1
public class DumbCollection : ICollection<int>

ונקבל:

1
2
3
4
if(typeof(ICollection<>).IsAssignableFrom(typeof(DumbCollection)))
{
// False
}

דוגמה שנייה:

עכשיו ניקח טיפוס גנרי, וננסה להבין אם הוא מממש את הממשק. מאחר שהדרך הפשוטה לא עובדת, נעשה משהו יותר מתוחכם:

1
2
3
4
5
6
7
Type unboundList =
typeof (List<int>).GetGenericTypeDefinition();
if (typeof(ICollection<>).IsAssignableFrom(unboundList))
{
// False
}

אפילו זה לא עובד…


אם ננסה להבין למה זה קורה, נראה כי הממשקים שמממש unboundList הם לא IsAssignableFrom מtypeof(ICollection<>):

1
2
3
4
5
6
7
IEnumerable<Type> types =
unboundList.GetInterfaces().Where(x => typeof(ICollection<>).IsAssignableFrom(x));
if (types.Any())
{
// False
}

אבל באופן מפתיע, אם נעשה משהו כזה:

(נרמלנו את הType)

1
2
3
4
5
6
7
8
9
IEnumerable<Type> types =
unboundList.GetInterfaces().
Where(x => x.IsGenericType &&
typeof(ICollection<>).IsAssignableFrom(x.GetGenericTypeDefinition()));
if (types.Any())
{
// True
}

יא-אללה.


בקיצור, נוכל לפתור את הסוגיה ע"י כתיבת Extension Method נחמד:

1
2
3
4
5
6
7
8
9
10
11
public static bool IsAssignableFromGenericInterface(thisType genericInterface, Type type)
{
IEnumerable<Type> collectionInterfaces =
from currentInterface in type.GetInterfaces()
where currentInterface.IsGenericType
let nonGeneric = currentInterface.GetGenericTypeDefinition()
where genericInterface.IsAssignableFrom(nonGeneric)
select currentInterface;
return collectionInterfaces.Any();
}

ואז נוכל לעשות דברים כאלה:

1
2
3
4
5
6
7
8
9
if(typeof(ICollection<>).IsAssignableFromGenericInterface(typeof(DumbCollection)))
{
// True
}
if(typeof(ICollection<>).IsAssignableFromGenericInterface(typeof(List<int>)))
{
// True
}

כמובן, נוכל לבצע בכניסה למתודה וולידציה על הפרמטרים, כגון האם genericInterface הוא unbound (ע"י IsGenericTypeDefinition) וכו’.

באופן דומה נוכל לכתוב מתודה שבודקת גם על הBaseTypes, ולא רק על הממשקים.

יום טיפוסי טוב

שתף

142. The difference between IsSubclassOf and IsAssignableFrom

לפעמים אנחנו מעוניינים לדעת האם Type מסוים הוא תת-טיפוס של Type נתון (כלומר, יורש מהType נתון, או מממש את הType נתון).

לכאורה נראה שיש פתרון פשוט לזה, באמצעות הפונקציה IsSubclassOf. אלא שפונקציה זו עושה בעיות במקרים הבאים:

  • אם הטיפוסים שווים, היא תחזיר false.

לדוגמה:

1
2
3
4
if (typeof(Shape).IsSubclassOf(typeof(Shape)))
{
// False
}
  • אם הטיפוס שאנחנו רוצים לבדוק, האם הטיפוס השני יורש ממנו, הוא ממשק, נקבל false.

לדוגמה, אם יש לנו מחלקה כזאת:

1
public class Shape : IDrawable

לא נכנס לתנאי (באופן אולי מפתיע)

1
2
3
4
if (typeof(Shape).IsSubclassOf(typeof(IDrawable)))
{
// False
}
  • בשאר המקרים (מחלקה שיורשת ממחלקה), הדבר הזה כן יעבוד:
1
2
3
4
if (typeof(Circle).IsSubclassOf(typeof(Shape)))
{
// True
}

כדי שבכל זאת נוכל לבדוק האם טיפוס יורש (במובן הC#י החלש - כלומר מממש/יורש/שווה) מטיפוס אחר, קיימת פונקציה אחרת בשם IsAssignableFrom. פונקציה זו היא פונקציה של Type המקבלת Type שני, ובודקת האם הType השני "יורש" (במובן הC#י החלש) מהType הראשון, או במילים אחרות, האם אפשר להסב (בהסבה סטנדרטית) את הטיפוס השני לטיפוס הראשון. לדוגמא:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (typeof(Shape).IsAssignableFrom(typeof(Shape)))
{
// True
}
if (typeof(IDrawable).IsAssignableFrom(typeof(Shape)))
{
// True
}
if (typeof(Shape).IsAssignableFrom(typeof(Circle)))
{
// True
}

שימו לב! יש פה דבר קצת מבלבל – אם קודם שמנו את הטיפוס שאנחנו מעוניינים לבדוק שיורשים ממנו בתור הפרמטר השני, עכשיו הוא הפרמטר הראשון – להשוואה:

typeof(Shape).IsAssignableFrom(typeof(Circle)) לעומת typeof(Circle).IsSubclassOf(typeof(Shape)).

המשך יום טיפוסי טוב

שתף