[英]C# Implementation of Event
我正在學習關於類的C#事件實現。
我有例子:
Car class繼承了Sport和City課程。 Car class具有由Sport和City類繼承的基本方法調用OnBuy。 在OnBuy方法中,事件處理程序已分配給Event Buy。
還有一個名為LicenseService的服務或類,每次購買時都會生成許可證號。
在這種情況下,我實現了事件驅動編程。 這是我的git示例:
https://github.com/adityosnrost/CSharpLearningEvent
問題:
這是在C#上使用Event的正確方法嗎?
如果這是正確的。 我可以將OnBuy方法覆蓋到每個孩子身上嗎? 如果覆蓋可用,我該怎么辦?
我可以做些什么來使這個樣本更好?
謝謝
class Program
{
static void Main(string[] args)
{
Car car = new Car();
Sport sport = new Sport();
City city = new City();
//car.Name();
//sport.Name();
//city.Name();
//Console.ReadLine();
LicenseService ls = new LicenseService();
city.Buy += ls.GenerateLicense;
city.OnBuy();
Console.ReadLine();
}
}
internal class Car
{
internal virtual void Name()
{
Console.WriteLine("Car");
}
internal event EventHandler Buy;
internal virtual void OnBuy()
{
EventHandler handler = Buy;
if (null != handler)
{
handler(this, EventArgs.Empty);
}
}
}
internal class Sport: Car
{
internal override void Name()
{
Console.WriteLine("Sport");
}
}
internal class City: Car
{
internal override void Name()
{
Console.WriteLine("City");
}
}
internal class LicenseService
{
internal void GenerateLicense(object sender, EventArgs args)
{
Random rnd = new Random();
string carType = sender.GetType().ToString();
string licenseNumber = "";
for(int i = 0; i < 5; i++)
{
licenseNumber += rnd.Next(0, 9).ToString();
}
Console.WriteLine("{1} Car has been bought, this is the license number: {0}", licenseNumber, carType);
}
}
如果我正在制作這個節目,我會做出以下更改,
首先它有兩個參數Object
和EventArgs
,你只需要Car
in handler方法(以及隨后的Random
討論原因)。
LicenseService
,並LicenseService
在構造函數中注冊(subscribe)Event。 那將是更清潔的方式。 CarName
,這樣每個孩子都可以在任何他們想要的地方使用它。 Buy
,而是我會命名為Bought
。 GenerateLicense()
內部每次都會創建Random
新對象。 因此,如果您對該方法的兩次調用都在很短的時間內,它將為兩個調用生成相同的隨機數。 為什么? 看到這個問題 - 或者您可以自己嘗試下面的示例代碼。 所以我會在GenerateLicense()
傳遞已經創建的Random
對象。 因此,對於該方法的每次調用, Random
都是常見的。 用於解釋隨機數行為的示例代碼
//as object of Random numbers are different,
//they will generate same numbers
Random r1 = new Random();
for(int i = 0; i < 5; i++)
Console.WriteLine(r1.Next(0, 9));
Random r2 = new Random();
for(int i = 0; i < 5; i++)
Console.WriteLine(r2.Next(0, 9));
更新
Events
暴露給外部代碼。 在這個答案中添加他的評論 兩點,
EventHandler Buy
不允許直接訪問外部它應該是私有的,因為任何人都可以將其設置為null並且所有訂閱都消失了。 它需要一個事件訪問器,因此可以使用+=
和-=
運算符訪問事件,並且它本身可以使多個訂戶的線程安全,否則會出現競爭條件,請查看一個簡單的示例
以下是代碼,
你的班級結構:
internal delegate void EventHandler(Car car, Random rnd);
internal class Car
{
internal string CarName;
internal virtual void SetName()
{
this.CarName = "car";
}
//Edit : As Mrinal Kamboj suggested in comments below
//here keeping event Buy as private will prevent it to be used from external code
private event EventHandler Buy;
//while having EventAccessros internal (or public) will expose the way to subscribe/unsubscribe it
internal event EventHandler BuyAccessor
{
add
{
lock (this)
{
Buy += value;
}
}
remove
{
lock (this)
{
Buy -= value;
}
}
}
internal virtual void OnBuy(Random rnd)
{
if (Buy != null)
Buy(this, rnd);
}
}
internal class Sport: Car
{
LicenseService m_ls;
internal Sport(LicenseService ls)
{
this.m_ls = ls;
this.BuyAccessor += ls.GenerateLicense;
SetName();
}
internal override void SetName()
{
this.CarName = "Sport";
}
}
internal class City: Car
{
LicenseService m_ls;
internal City(LicenseService ls)
{
this.m_ls = ls;
this.BuyAccessor += ls.GenerateLicense;
SetName();
}
internal override void SetName()
{
this.CarName = "City";
}
}
LicenseService
類
internal class LicenseService
{
internal void GenerateLicense(Car sender, Random rnd)
{
string carName = sender.CarName;
string licenseNumber = "";
for(int i = 0; i < 5; i++)
{
licenseNumber += rnd.Next(0, 9).ToString();
}
Console.WriteLine("{1} Car has been bought, this is the license number: {0}", licenseNumber, carName);
}
}
並呼吁流動
static void Main(string[] args)
{
Random rnd = new Random();
LicenseService ls = new LicenseService();
Sport sport = new Sport(ls);
City city = new City(ls);
city.OnBuy(rnd);
sport.OnBuy(rnd);
Console.ReadLine();
}
這是一個很長的答案,但我會首先解釋你的例子,然后繼續一些一般性的提示。 請記住,所有代碼都是偽代碼,如果沒有語法調整,它將無法編譯。
首先,你的邏輯結構沒有意義,這就是為什么你可能很難確定這是否正確。
例如,在現實世界中,您不會在汽車上購買它,而是在商店或銷售它們的服務中找到它們。 您只能駕駛汽車或使用其提供的其他功能。 汽車沒有為自己分配許可證。 最后,如果您采用基本的賣家/買家示例,則購買是一個通常是線性的(並且可以通過方法表示,沒有觸發器)。 因此,當您調用shop.BuyCar( sportsCar )
,可以從buy方法調用所有購買邏輯。
Class Shop{
public Car BuyCar( carWithoutLicense ){
//Purchase logic
LicenseService.AssignLicense( carWithoutLicense ).
return carWithoutLicense.
}
}
//A person would call this method, no the car
正確使用事件的一個更好的例子是汽車前面板上的警示燈之一,因為它可以通知駕駛員他/她可能想要做出的反應。 例如:檢查發動機燈。
class Car {
Bool CheckEngingLightIsOn = false;
public void Ride( ... ){
if( enginge.faultDetected == true ){
TurnOnCheckEngineAlert( );
}
}
public void TurnOnCheckEngineAlert( ){
CheckEngingLightIsOn = true;
if( CheckEngineLightSwitch != null ){
CheckEngineLightSwitch.Invoke( ... )
}
}
}
class Driver {
public Driver( Car car ){
this.car = car;
if( driverState != Drunk ){
car.CheckEngineLightSwitch = TakeAction;
}
}
public Drive( ){
car.Ride( );
}
public void TakeAction( Car car, EventArgs e ){
//get out, open the hood, check the engine...
if( car.CheckEngingLightIsOn == true ){ "Light turned on
//Check Engine
}else{
//continue driving
}
}
}
如果不深入抽象,請注意事件鏈:
Driver
駕駛汽車並且在發生之前不擔心其他事情(例如檢查發動機燈)。 Car
檢測到故障,它會打開檢查引擎燈,並且該事件(訂戶)上有事件處理程序,汽車會觸發事件。 此示例與您的示例根本不同,因為:
換句話說,購買過程是固定的(支付,許可,貨物轉移)和跳過必不可少的步驟不能跳過。 汽車自行移動的過程並非一成不變,因為汽車和司機都不知道旅程中會發生什么。 IE驅動程序可能會在沒有停止的情況下開車到達目的地(如果沒有出現故障或者他沒有注意到燈光),或者當車輛注意到檢查發動機指示燈亮起時汽車可能會強迫駕駛員停車(基本上,他們都在相互控制感覺)。
關於您的用例的一些常規提示
在你的例子中你做了一個有點復雜的用例(錯誤放置邏輯),它將運行,但結構上是不正確的(在設計更多邏輯時,錯誤的邏輯結構往往會導致人為錯誤)。
您應該首先看到的是與每個對象相關的邏輯。 對象中的事件/方法是代表對象所做的事情(即對象自身執行的功能)還是影響對象的東西,但對象本身在進程中沒有做任何事情? 例如:汽車自行“騎”(即使這個過程開始,其所有參數,如速度或方向都由駕駛員控制); 為汽車分配許可證完全發生在汽車結構之外,並且汽車只會在過程中獲得屬性更改 (許可證)。
這種區別很重要,因為只有對象執行的邏輯才與該對象相關,並且通過擴展,任何由另一個對象執行並且僅影響您的對象的邏輯都屬於其他地方。 因此, Buy
絕對不屬於汽車而且Ride(移動過程)屬於汽車。
其次,您的命名將有助於您理解此主題。 方法代表的行動和應該被命名為這樣( Shop.BuyCar
, Car.Ride
, Driver.Drive
),事件代表反應觸發器 ( Car.CheckEngineLightSwitch
)和事件處理程序表示反應的動作(反應仍然是一個動作,所以沒有需要特殊命名,但您可以命名為區分動作和反應)。
不。
除了擁有該事件的類之外,任何人都不應該調用事件(或者,如果您知道自己在做什么,那么繼承自擁有該事件的類的類 - 廣告即使這樣,他們也應該注意原始事件實施以避免細微問題)
基本上,對事件的訂閱是一種承諾,當給定的事情發生時,您將調用您從訂閱者那里獲得的功能。 事件本身很簡單,代碼構造允許您進行訂閱,而無需知道或實現多播函數指針調用的復雜性。
否則,你只是在調用一個函數,也可能不會對事件感到煩惱。
事件本質上是有意的代碼注入 - 它們允許你讓一些其他類執行你編寫的任意代碼,當他們做某事時。
我建議你向汽車注入許可證服務,並在購買時調用生成許可證功能,
using System;
namespace ConsoleApp1.Test
{
class Program
{
static void Maintest(string[] args)
{
ILicenseService licenseService = new LicenseService();
Sport sport = new Sport(licenseService);
City city = new City(licenseService);
//car.Name();
//sport.Name();
//city.Name();
//Console.ReadLine();
city.OnBuy();
Console.ReadLine();
}
}
internal abstract class Car
{
protected readonly ILicenseService licenseService;
public Car(ILicenseService _licenseService)
{
licenseService = _licenseService;
}
internal virtual void Name()
{
Console.WriteLine("Car");
}
internal event EventHandler Buy;
internal virtual void OnBuy()
{
// TODO
}
}
internal class Sport : Car
{
public Sport(ILicenseService _licenseService) : base(_licenseService)
{
}
internal override void Name()
{
Console.WriteLine("Sport");
}
internal override void OnBuy()
{
licenseService.GenerateLicense(new object());
}
}
internal class City : Car
{
public City(ILicenseService _licenseService) : base(_licenseService)
{
}
internal override void Name()
{
Console.WriteLine("City");
}
internal override void OnBuy()
{
licenseService.GenerateLicense(new object());
}
}
internal interface ILicenseService
{
void GenerateLicense(object param);
}
internal class LicenseService : ILicenseService
{
public void GenerateLicense(object param)
{
// do your stuff
}
}
}
1)這是在C#上使用Event的正確方法嗎?
不完全的。 OnBuy應該是受保護的虛擬方法。 這也排除了你從Main()方法調用它。
更常見的是調用someCar.Buy()然后Buy()將觸發OnBuy()。
2)如果這是正確的。 我可以將OnBuy方法覆蓋到每個孩子身上嗎? 如果覆蓋可用,我該怎么辦?
是的,你可以覆蓋它看到這是一種更有效的訂閱自己的方式(這將是另一種選擇)。
購買特定類型的汽車時,您可以執行任何操作。 但是做alwas call base.OnBuy()
3)從這個樣本中我可以做些什么來改善它?
CreateLicense聽起來不像是一個事件的好候選者,它更像是CarDealer調用的業務規則。
遵循現代設計規則,汽車將是一個相當被動的實體對象(貧血領域模型)。
事件通常用於告訴其他組件“我已經改變,做你的事”,而不是對自己執行必要的操作。
以下是我的首選設計:
class Program
{
static void Main(string[] args)
{
Car car = new Sport();
car.BuyEvent += License.GenerateLicense;
car.OnBuy();
car = new City();
car.BuyEvent += License.GenerateLicense;
car.OnBuy();
}
}
internal abstract class Car
{
internal abstract void Name();
protected abstract event EventHandler Buy;
public abstract event EventHandler BuyEvent;
public abstract void OnBuy();
}
internal class Sport : Car
{
internal override void Name()
{
Console.WriteLine("Sport Car");
}
protected override event EventHandler Buy;
public override event EventHandler BuyEvent
{
add
{
lock (this)
{
Buy += value;
}
}
remove
{
lock (this)
{
Buy -= value;
}
}
}
public override void OnBuy()
{
if (Buy != null)
{
Buy(this, EventArgs.Empty);
}
}
}
internal class City : Car
{
internal override void Name()
{
Console.WriteLine("City Car");
}
protected override event EventHandler Buy;
public override event EventHandler BuyEvent
{
add
{
lock (this)
{
Buy += value;
}
}
remove
{
lock (this)
{
Buy -= value;
}
}
}
public override void OnBuy()
{
if (Buy != null)
{
Buy(this, EventArgs.Empty);
}
}
}
internal static class License
{
public static void GenerateLicense(object sender, EventArgs args)
{
Random rnd = new Random();
string carType = sender.GetType().Name;
string licenseNumber = "";
for (int i = 0; i < 5; i++)
{
licenseNumber += rnd.Next(0, 9).ToString();
}
Console.WriteLine($"{carType} Car has been bought, this is the license number: {licenseNumber}");
}
}
重點:
Car
基類抽象,它只能像City / Sport Car
一樣派生和使用 event add / remove accessor
以自定義事件處理。 使訪問者線程對共享客戶端訪問安全 License - GenerateLicense
是靜態的,因為類中沒有狀態/數據,或者通過另一個基本的抽象方法將它們與City / Sport class
實現集成在一起 Car
應在運行時注入Sport
\\ City
對象,並應通過基類Car
對象用於事件訂閱
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.