C# 4.0 Part 2 (The "GetCalculator program)
אז כמו שהבטחתי
בפוסט הקודם אני אדגים שימוש אמיתי ב - dynamic,
אפשר להוריד את התוכנית
מכאן (נפתח ב VS2010)
כל מי שקרה מאמר או פוסט כלשהו על C# 4.0 ראה את הדוגמא הבאה (להסבר על dynamic)
במקום לכתוב את הקוד הזה:
object o = GetCalculator();
MethodInfo mi = o.GetType().GetMethod("Add");
int res = (int)mi.Invoke(o, new object[] { 2, 3 });
אפשר לכתוב את הקוד הבא:
dynamic o = GetCalculator();
int res = o.Add(2, 3);
הדוגמא הזאת תמיד הטרידה אותי,
הרי אי אפשר להפעיל מתודות שהם לא public באמצעות dynamic, אז בהכרח המתודות של ה - Calculator הם public, אז מדוע שמישהו ירצה להפעיל את מתודת Add ב - Reflection ?
גם אם אני לא מכיר את האובייקט האמיתי, אני יכול עדיין להשתמש ב - Interface.
חרשתי את הרשת בנסיון למצוא דוגמא אמיתית ל - GetCalculator, אבל לא מצאתי
ולכן כתבתי את הדוגמא הבאה - שמראה מימוש של מחשבון ב - net ומימוש ב - JS, והפעלת המתודות על ידי dynamic:
בשלב ראשון נבנה פרויקט בשם CalculatorImplementation שמכיל class בשם NetCalculator
public class NetCalculator
{
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
public int Multiply(int a, int b)
{
return a * b;
}
public int Divide(int a, int b)
{
return a / b;
}
}
כעת נוסיף לפרויקט קובץ Htm בשם JSCalculator.htm
<head>
<script type="text/javascript">
var Calculator = new Object();
Calculator.Add = function(a, b) { return a + b; };
Calculator.Subtract = function(a, b) { return a - b; };
Calculator.Multiply = function(a, b) { return a * b; };
Calculator.Divide = function(a, b) { return a / b; };
function GetCalculator() {
return Calculator;
}
</script>
</head>
למעשה זה מימוש פשוט של אובייקט מחשבון ב - JS.
נסמן במאפיין Copy To Output Directory את האופצייה של Copy always (כדי שיהיה לנו את קובץ ה - htm אחרי קומפילציה)
היות שאנחנו לא יכולים לגשת ישירות ל JS מתוך WinFormApplication אנחנו צריכים מישהו שיארח אותו, לכן נוסיף לפרויקט (שהוא מסוג ClassLibrary) קובץ מסוג WinForm שיש בו WebBrowser שה - URL שלו הוא לקובץ ה - htm שיצרנו, והוא למעשה יהיה ה - Host ל JS.
הקובץ נקרא JSHost
כאמור אין בו כלום מלבד WebBrowser - והוא נראה כך:
הקוד היחיד שלו ב - Ctor נראה כך:
public JSHost()
{
InitializeComponent();
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"JSCalculator.htm");
webBrowser1.Url = new Uri(path);
}
הקוד טוען את ה - html שלנו לתוך ה - browser והוא יארח אותו ונוכל לגשת אליו (מה שהוא מחזיר לנו זה אובייקט Com)
כעת נוסיף לפרויקט class בשם CalculatorFactory, שיודע להחזיר לפי קונפיגורציה את המחשבון המתאים. (ההסברים מיד אחרי הקוד)
public class CalculatorFactory
{
private JSHost m_JsHost;
private object m_CalculatorObject;
private static CalculatorFactory m_Instance;
public static CalculatorFactory Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new CalculatorFactory();
}
return m_Instance;
}
}
private CalculatorFactory()
{
m_JsHost = new JSHost();
m_JsHost.Show();
m_JsHost.Hide();
}
public object Calculator
{
get
{
if (m_CalculatorObject == null)
{
if (CalculatorType == "Net")
{
m_CalculatorObject = new NetCalculator();
}
else if (CalculatorType == "JS")
{
m_CalculatorObject = m_JsHost.webBrowser1.Document.InvokeScript("GetCalculator");
}
else
{
m_CalculatorObject = null;
}
}
return m_CalculatorObject;
}
}
public string CalculatorType
{
get
{
string type = ConfigurationManager.AppSettings["CalculatorType"];
return type;
}
}
}
אז ככה:
ה - class מכיל שלושה משתנים
הראשון הוא מופע של ה - JSHost,
השני הוא אובייקט המחשבון,
והשלישי הוא מופע של ה - Factory (מימוש פשוט של Singleton)
אחרי הגדרת המשתנים יש את המאפיין שמחזיר מופע בודד של ה - Factory.
ב - Ctor אני מייצר את ה - JSHsot, מפעיל את מתודת ה - Show ומיד את מתודת Hide, הסיבה לזה היא מאוד פשוטה, מכיון שכל עוד שה - Form לא יהיה במצב Show, ה - WebBrowser לא יארח את קובץ ה - htm, אבל מכיון שאני לא באמת רוצה לראות את ה - Form, אני מסתיר אותו.
המאפיין הבא מחזיר אם אובייקט המחשבון לפי מה שמוגדר בקובץ הקונפיגורציה, במידה ומוגדר Net אני מחזיר את ה - NetCalculator במידה ומוגדר JS אני מריץ את הסקריפט getCalculator שמחזיר את המחשבון מקובץ ה - JS (עטוף כאמור ב - COM)
והמאפיין האחרון מחזיר מה שמוגדר בקובץ הקונפיגורציה.
כעת נראה את השימוש בזה.
נוסיף פרויקט חדש מסוג WinFormApplication בשם CalculatorDemo,
הנה קובץ הקונפיגורציה:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CalculatorType" value="JS"/>
</appSettings>
</configuration>
כעת נוסיף את קובץ ה - GUI הוא נראה כך:
אני מראה את הקוד של ה - Ctor והלחצן Add:
public Form1()
{
InitializeComponent();
this.Text = "Calculator Demo: " + CalculatorFactory.Instance.CalculatorType;
}
private void btnAdd_Click(object sender, EventArgs e)
{
Type calcType = CalculatorFactory.Instance.Calculator.GetType();
int num1 = int.Parse(txtNum1.Text);
int num2 = int.Parse(txtNum2.Text);
int resault = Convert.ToInt32(
calcType.InvokeMember("Add",
BindingFlags.InvokeMethod,
null,
CalculatorFactory.Instance.Calculator,
new object[] { num1, num2 }));
txtResault.Text = resault.ToString();
}
והנה אנחנו רואים קוד שניגש לאוביקט מה - Factory ומפעיל את המתודות שלו באמצעות Reflection , היות שאנחנו מקבלים מה - Factory אובייקט מסוג object שיכול להגיע מדוט נט או מ - COM, אנחנו חייבים להפעיל את זה באמצעות Reflection.
כעת נעשה שינוי אחד קטן ב - Factory, המאפיין שנקרא Calculator יחזיר dynamic במקום object, שימו לב:
//public object Calculator
public dynamic Calculator
זהו, זה השינוי היחיד, עכשיו שימו לב לקוד של הלחצן Add:
private void btnAdd_Click(object sender, EventArgs e)
{
int num1 = int.Parse(txtNum1.Text);
int num2 = int.Parse(txtNum2.Text);
int resault = CalculatorFactory.Instance.Calculator.Add(num1, num2);
txtResault.Text = resault.ToString();
}
מדהים איך ששינוי קטן (להחזיר dynamic במקום object) גורם שנוכל לכתוב את הקוד שלנו בצורה הגיונית.
מקווה שהפוסט הזה והקודם הצליחו להדגים את השימוש ב - dynamic, בפוסטים הבאים נכנס לעומק של הדברים ונבדוק מה זה בעצם dynamic, ומה מתחולל מאחורי הקלעים.