Moving dynamic method call to class library causes a RuntimeBinderException in C# - c#

I was reading an interesting article using of DataFlow + dynamic method invocation to make an Actor model in C#. Here is the completed example verbatim.
using System;
using System.Threading.Tasks.Dataflow;
namespace ConsoleApplication
{
public abstract class Message { }
public abstract class Actor
{
private readonly ActionBlock<Message> _action;
public Actor()
{
_action = new ActionBlock<Message>(message =>
{
dynamic self = this;
dynamic mess = message;
self.Handle(mess);
});
}
public void Send(Message message)
{
_action.Post(message);
}
}
class Program
{
public class Deposit : Message
{
public decimal Amount { get; set; }
}
public class QueryBalance : Message
{
public Actor Receiver { get; set; }
}
public class Balance : Message
{
public decimal Amount { get; set; }
}
public class AccountActor : Actor
{
private decimal _balance;
public void Handle(Deposit message)
{
_balance += message.Amount;
}
public void Handle(QueryBalance message)
{
message.Receiver.Send(new Balance { Amount = _balance });
}
}
public class OutputActor : Actor
{
public void Handle(Balance message)
{
Console.WriteLine("Balance is {0}", message.Amount);
}
}
static void Main(string[] args)
{
var account = new AccountActor();
var output = new OutputActor();
account.Send(new Deposit { Amount = 50 });
account.Send(new QueryBalance { Receiver = output });
Console.WriteLine("Done!");
Console.ReadLine();
}
}
}
This works as intended. Moving Actor & Message classes into a new class library and referencing properly causing an issue. When run it throws a RuntimeBinderException on the dynamic self.Handle(mess); within the Actor constructor saying Actors.Actor does not contain a definition for 'Handle'. Are there limitations to dynamic method calls I can't seem to find in the MSDN or syntax magic I missing to do this from a separate class library?

The original author got back to me.
Hi,
The problem is that you have declared your messages and actors inside
the internal NotWorkingProgram class.
class NotWorkingProgram // no access modifier! Default is 'internal' {
public class Deposit : Message
...
public class AccountActor : Actor
{
public void Handle(Deposit message)
...
} }
When you run the program the runtime tries to find a method named
'Handle' with a parameter of typ 'Deposit'. It can't find anything
because the AccountActor class is not visible from the Actors Project.
It is hidden inside the invisible NotWorkingProgram. If you make the
NotWorkingProgram class public (or move the Deposit and AccountActor
classes outside) it works!
Regards Johan
I'm leaving this here cause the RuntimeBinderException doesn't give much info, let alone any hint of class/method privacy being a possible root

Related

Which design pattern to use when we have classes that does similar high level functionality but the returns different types in methods?

I have an existing C# console application that takes arguments and based on the arguments
creates an instance of markets (UK, US, MX..) using dependency injection.
Each market class does a 'string GetData()', 'string ProcessData()' and 'bool ExportData()'.
The application was initially created for one eCommerce vendor's markets. Now I am told to modify it for a different vendor that does a different process. The high-level flow remains the same.
'GetData' to fetch records from DB,
'ProcessData' for any transformation or the likes
'ExportData'.
The difference is Getdata() pulls records from DB and maps to an object. I am planning to use Petapoco. 'ProcessData' might return a similar class. 'Exportdata' currently does an API call but for the new vendor, I have to write to a file.
I was reading up on patterns I am totally confused. At first, I thought I needed abstract factory pattern and now I think the factory method is what I should be using but I am not sure if I am doing it right. Need some guidance/review here. A sample cs file I created from my understanding of factory pattern. This code is based on the headfirst code samples.
using System;
using System.Collections.Generic;
using StatusExport.Models;
namespace factorymethod
{
class Program
{
static void Main(string[] args)
{
ClientFactory factory = null;
Console.WriteLine("Enter client code:");
string clientCode= Console.ReadLine();
switch (clientCode.ToLower())
{
case "costco":
factory = new CostcoFactory("accountname", "taskname");
break;
//NEw vendor might be added
//case "walmart"
//factory = new WalmartFactory("taskname", "type");
//break
default:
break;
}
bool status = factory.ProcessData();
Console.ReadKey();
}
}
abstract class Client
{
public abstract string AccountName { get; }
public abstract string Task { get; set; }
//More properties might be added. Some may not even be used by some of the new vendors. For example, Costco Might need accountname and task. Tomorrow if walmart comes, they might not need these two or may need task and a new property 'type'
public abstract List<T> GetData<T>();
public abstract List<T> ProcessData<T>();
public abstract bool ExportData();
}
class CostcoClient : Client
{
public override string AccountName { get; }
public override string Task { get; set; }
public CostcoClient(string accountName, string task)
{
AccountName = accountName;
Task = task;
}
public override List<DBRecord> GetData<DBRecord>() //DBRecord class is specific to Costco.
{
List<DBRecord> dbresult = new List<DBRecord>();
//dbresult = db return data mapped to an object DBRecord using petapoco. Another vendor might have a different class to which DB records are mapped. So the return type can be generic
return asn;
}
public override List<T> ProcessData<T>()
{
throw new NotImplementedException(); //Any data transformation or business logic. Return type might be DBRecord or a new class altogether
}
public override bool ExportData()
{
throw new NotImplementedException();//Call API or write data to file and if success send true else false
}
}
abstract class ClientFactory
{
public abstract bool ProcessData();
}
class CostcoFactory : ClientFactory
{
public string AccountName { get; }
public string Task { get; set; }
public CostcoFactory(string accountname, string task)
{
AccountName = accountname;
Task = task;
}
public override bool ProcessData()
{
CostcoClient gc = new CostcoClient(AccountName, Task);
var result = gc.GetData<DBRecord>();
return true;
}
}
}
Do you think this is the right design approach?
I also want to keep the console project independent of vendor project. So maybe 'StatusExport.Program' for the console application. DLL projects StatusExport.Common to hold the interface and abstract classes' and 'StatusExport.Client(ex:StatusExport.Costco)' for each vendor stuff.
You can create BaseClient class that will contains a basic group of properties, and if you need to add something new - just inherit it. You did right, but i think it's better to change public modifier to protected in your properties AccountName and Task, to give access to them only from child classes.
Actually, you can create a BaseClientModels (request/response) for each method if you are not sure that returning type List will be always actual.
Example:
public abstract class BaseClient
{
#region Properties : Protected
protected abstract string AccountName { get; }
protected abstract string Task { get; set; }
#endregion
#region Methods : Public
public abstract BaseGetDataResponseModel GetData(BaseGetDataRequestModel model);
public abstract BaseProcessDataResponseModel ProcessData(BaseProcessDataRequestModel model);
public abstract BaseExportDataResponseModel ExportData(BaseExportDataRequestModel model);
#endregion
}
public class BaseGetDataResponseModel { }
public class BaseGetDataRequestModel { }
public class BaseProcessDataResponseModel { }
public class BaseProcessDataRequestModel { }
public class BaseExportDataResponseModel { }
public class BaseExportDataRequestModel { }
Then let's look on your class CostcoClient and how it can looks like:
public class CostcoClient : BaseClient
{
#region Properties : Protected
protected override string AccountName { get; }
protected override string Task { get; set; }
protected virtual IDataReader<BaseGetDataRequestModel, BaseGetDataResponseModel> DataReader { get; }
protected virtual IDataProcessor<CostcoClientProcessDataRequestModel, CostcoClientProcessDataResponseModel> DataProcessor { get; }
protected virtual IExportDataHandler<CostcoClientExportDataRequestModel, CostcoClientExportDataResponseModel> ExportDataHandler { get; }
#endregion
#region Constructors
public CostcoClient(string accountName, string task)
{
//set DataReader, DataProcessor, ExportDataHandler
AccountName = accountName;
Task = task;
}
#endregion
#region Methods : Public
public override BaseGetDataResponseModel GetData(BaseGetDataRequestModel model)
{
if (model is CostcoClientGetDataRequestModel clientGetDataRequestModel)
{
return DataReader.ReadData(clientGetDataRequestModel);
}
return null; //wrong type has passed
}
public override BaseProcessDataResponseModel ProcessData(BaseProcessDataRequestModel model)
{
if (model is CostcoClientProcessDataRequestModel clientProcessDataRequestModel)
{
return DataProcessor.ProcessData(clientProcessDataRequestModel);
}
return null;
}
public override BaseExportDataResponseModel ExportData(BaseExportDataRequestModel model)
{
if (model is CostcoClientExportDataRequestModel clientExportDataRequestModel)
{
return ExportDataHandler.Handle(clientExportDataRequestModel);
}
return null;
}
#endregion
}
public class CostcoClientGetDataRequestModel : BaseGetDataRequestModel { }
public class CostcoClientGetDataResponseModel : BaseGetDataResponseModel { }
public class CostcoClientProcessDataRequestModel : BaseProcessDataRequestModel { }
public class CostcoClientProcessDataResponseModel : BaseProcessDataResponseModel { }
public class CostcoClientExportDataRequestModel : BaseExportDataRequestModel { }
public class CostcoClientExportDataResponseModel : BaseExportDataResponseModel { }
public interface IDataReader<TIn, TOut>
{
public TOut ReadData(TIn model);
}
public interface IDataProcessor<TIn, TOut>
{
public TOut ProcessData(TIn model);
}
public interface IExportDataHandler<TIn, TOut>
{
public TOut Handle(TIn model);
}
public class CostcoClientDataReader : IDataReader<CostcoClientGetDataRequestModel, CostcoClientGetDataResponseModel>
{
public CostcoClientGetDataResponseModel ReadData(CostcoClientGetDataRequestModel model)
{
throw new NotImplementedException();
}
}
//and so on
You have to implement IDataReader, IDataProcessor, IExportDataHandler, make your logic and call it from GetData, ProcessData, ExportData methods, as an example, and get instances via dependency injection.
Then, we can change your factory to this:
public interface IClientFactory
{
BaseClient GetClientService(ClientServicesEnum value);
}
public class BaseClientFactory : IClientFactory
{
#region Propertied : Protected
protected virtual IEnumerable<BaseClient> Services { get; }
protected string AccountName { get; }
protected string Task { get; set; }
#endregion
#region Constructors
public BaseClientFactory(IEnumerable<BaseClient> services, string accountname, string task)
{
Services = services;
AccountName = accountname;
Task = task;
}
#endregion
public BaseClient GetClientService(ClientServicesEnum value)
=> Services.First(x => x.GetType().Equals(GetClientServiceByCode()[value]));
private Dictionary<ClientServicesEnum, Type> GetClientServiceByCode()
=> new Dictionary<ClientServicesEnum, Type>()
{
{ ClientServicesEnum.CostcoClient, typeof(CostcoClient) }
};
}
public enum ClientServicesEnum
{
CostcoClient = 1,
Another2 = 2,
Another3 = 3
}
Where
protected virtual IEnumerable<BaseClient> Services { get; }
you can get via DI too, and then get correct ServiceHandler by enum.
And your main function to call all this:
switch (clientCode)
{
case 1:
baseClient = ClientFactory.GetClientService(ClientServicesEnum.CostcoClient);
break;
case 2:
baseClient = ClientFactory.GetClientService(ClientServicesEnum.Another2);
break;
default:
break;
}
bool status = baseClient.ProcessData(null); //your model
The main thing is - you can use more than one pattern, for example one from Creational patterns, and one from Structural.
If i need some help in code architecture i use this:
https://refactoring.guru/
I think, using this example you can remove properties AccountName and Task, because of request models in methods.

C# restricting use of a class

The below code does what I would like it to do. The code in the Main method looks and behaves exactly as desired. However, it would be preferable if the class UserMenu, Home and DropdownMenu2 could only be used by the HeaderNavigationMenu to protect other developers from trying to used them outside of the HeaderNavigationMenu class. Additionally most articles frown upon making everything public.
Question:
Is the design patter being used below appropriate or is there something better and more acceptable to use in this scenario?
Edit: The reason for this design.
I wanted the end user of HeaderNavigationMenu to just be able to use the dot notation to get a list of available options. This Architecture accomplishes this goal (ex: navigationMenu.DropdownMenu2.SelectOption3())
Wanted anyone else who eventually might need to edit the code to understand that the classes UserMenu, Home and DropDownMenu2 where very specifically designed to be implemented by HeaderNavigationMenu class.
public class HeaderNavigationMenu
{
public HeaderNavigationMenu()
{
UsersMenu = new UsersMenu();
Home = new Home();
DropdownMenu2 = new DropdownMenu2();
}
public UsersMenu UsersMenu { get; set; }
public Home Home { get; set; }
public DropdownMenu2 DropdownMenu2 { get; set; }
}
public class UsersMenu
{
...
}
public class Home
{
...
}
public class DropdownMenu2
{
public void SelectOption3()
{
...
}
...
}
static void Main(string[] args)
{
HeaderNavigationMenu navigationMenu = new HeaderNavigationMenu();
navigationMenu.DropdownMenu2.SelectOption3();
// The following code is an example of undesired capability;
// prefer if Home class could only be
// used by HeaderNavigationMenu class
Home home = new Home();
}
Restrict access to the class constructors. If they are declared as "internal" then the classes may only be created by your code.
If you're looking to protect against the instantiation of UsersMenu, DropdownMenu2, and Home from outside HeaderNavigationMenu but still within the same project as HeaderNavigationMenu then there is a neat trick that can achieve this behavior. You can use public nested classes with private constructors which statically initialize their own factory methods. The basic template for this would be:
public class Outer{
private static Func<Inner> _innerFactory;
public Inner ExposedInner {get; private set;}
public Outer(){
// Force the static initializer to run.
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Inner).TypeHandle);
// Call the newly created factory method instead of a regular constructor.
ExposedInner = _innerFactory();
}
public class Inner {
static Inner(){
// Initialize Outer's static factory method.
_innerFactory = () => new Inner();
}
// Inner cannot be instantiated (without reflection) because its constructor is private.
private Inner(){}
// This method is now exposed for anyone to use.
public void DoStuff(){ Console.WriteLine("Did stuff"); }
}
}
Here's this concept implemented in your example:
class Program
{
static void Main(string[] args)
{
HeaderNavigationMenu navigationMenu = new HeaderNavigationMenu();
navigationMenu.DropdownMenu2.SelectOption3();
// This line will no longer work because the constructors
// for the inner classes are private.
HeaderNavigationMenu.HomeImpl home = new HeaderNavigationMenu.HomeImpl();
Console.ReadKey();
}
}
public class HeaderNavigationMenu
{
//Private factory methods that are statically initialized
private static Func<UsersMenuImpl> _createUsers;
private static Func<DropdownMenu2Impl> _createDropdown;
private static Func<HomeImpl> _createHome;
public HeaderNavigationMenu()
{
//Force the static constructors to run
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(UsersMenuImpl).TypeHandle);
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(HomeImpl).TypeHandle);
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(DropdownMenu2Impl).TypeHandle);
UsersMenu = _createUsers();
Home = _createHome();
DropdownMenu2 = _createDropdown();
}
public UsersMenuImpl UsersMenu { get; set; }
public HomeImpl Home { get; set; }
public DropdownMenu2Impl DropdownMenu2 { get; set; }
public class UsersMenuImpl
{
//Static constructor to make the class factory method
static UsersMenuImpl()
{
_createUsers = () => new UsersMenuImpl();
}
private UsersMenuImpl() { }
}
public class HomeImpl
{
//Static constructor to make the class factory method
static HomeImpl()
{
_createHome = () => new HomeImpl();
}
private HomeImpl() { }
}
public class DropdownMenu2Impl
{
//Static constructor to make the class factory method
static DropdownMenu2Impl()
{
_createDropdown = () => new DropdownMenu2Impl();
}
private DropdownMenu2Impl() { }
public void SelectOption3()
{
}
}
}
With this, you will still be able to use all the public properties of the inner classes however no one will be able to instantiate the inner classes from outside HeaderNavigationMenu and only HeaderNavigationMenu has access to the factory methods.
I don't really understand what your use case is and I've never coded like this but one way of only exposing the required behaviour of HeaderNavigationMenu would be to make the classes internal and the variables private and then expose only the SelectOption3() method, as below.
If you uncomment the line
//Home home = new Home();
you will get a compiler error.
class Program
{
static void Main(string[] args)
{
HeaderNavigationMenu navigationMenu = new HeaderNavigationMenu();
navigationMenu.DropdownMenu2SelectOption3();
// The following code is an example of undesired capability;
// prefer if Home class could only be
// used by HeaderNavigationMenu class
//Home home = new Home();
}
}
public class HeaderNavigationMenu
{
UsersMenu usersMenu;
Home home;
DropdownMenu2 dropdownMenu2;
public HeaderNavigationMenu()
{
usersMenu = new UsersMenu();
home = new Home();
dropdownMenu2 = new DropdownMenu2();
}
public void DropdownMenu2SelectOption3()
{
dropdownMenu2.SelectOption3();
}
class UsersMenu
{
}
class Home
{
}
class DropdownMenu2
{
public void SelectOption3()
{
}
}
}
You could make UsersMenu, Home, and DropdownMenu2 public abstract classes. Then have private classes nested inside of HeaderNavigationMenu which extend the public abstract versions.
public abstract class UsersMenu
{
}
public abstract class Home
{
}
public abstract class DropdownMenu2
{
public void SelectOption3()
{
// Code for SelectOption3...
}
}
public class HeaderNavigationMenu
{
public HeaderNavigationMenu()
{
UsersMenu = new UsersMenuImpl();
Home = new HomeImpl();
DropdownMenu2 = new DropdownMenu2Impl();
}
public UsersMenu UsersMenu { get; }
public Home Home { get; }
public DropdownMenu2 DropdownMenu2 { get; }
private class UsersMenuImpl : UsersMenu
{
}
private class HomeImpl : Home
{
}
private class DropdownMenu2Impl : DropdownMenu2
{
}
}
Fellow developers can see and use the UsersMenu, Home, and DropdownMenu2 abstract classes, but cannot create instances of them. Only HeaderNavigationMenu can.
Of course, another developer could always create their own classes deriving from the public abstract ones, but there is only so much you can do. UsersMenu, Home, and DropdownMenu2 have to be public in order to be public properties.

How to pass a function as delegate and force the compiler to restrict only a function that belongs to a class that implements a specific interface

I am working on a class that will have a multiple instances and different delegate function will be passed via the constructor for each object.
how can i pass a function as delegate to MissionGenerator class and force the compiler (at design time) to restrict only a function that belongs to a class that implements IDelegateMission interface. or maybe even better, if possible: belongs to class that implements IDelegateMission and from "type" RaiseTask ().
please take a look at the code below, it shows quite clear what I wish to achive.
// delegate type for tasks
public delegate void TaskDelegate();
public class MissionGenerator
{
protected TaskDelegate MissionToInvoke;
public MissionGenerator(TaskDelegate mission)
{
this.MissionName = MissionName;
}
}
interface IDelegateMission
{
System.DayOfWeek ExecutionDay { get; set; }
int HourOfExecution { get; set; }
void RaiseTask();
}
class Skarim: IDelegateMission
{
// class implement all IDelegateMission methods...
public void RaiseTask()
{
// this is the function to pass as TaskDelegate...
}
}
class MainClass
{
private void MainMethod()
{
MissionGenerator sekerMission = new MissionGenerator(new Skarim().RaiseTask);
}
}
Thank you.
Building on Evk's comment, do something like this:
public delegate void TaskDelegate();
public class MissionGenerator
{
protected TaskDelegate MissionToInvoke;
public MissionGenerator(IDelegateMission mission)
{
MissionToInvoke = mission.RaiseTask;
}
public void StartMission() => MissionToInvoke();
}
public interface IDelegateMission
{
void RaiseTask();
}
public class Skarim : IDelegateMission
{
public void RaiseTask() => Debug.WriteLine("Yo.");
}
public class MainClass
{
private void MainMethod()
{
var sekerMission = new MissionGenerator(new Skarim());
sekerMission.StartMission();
}
}

C# Handle Event

I am trying to make sort of a library and i am trying to grasp how can i implement it they way i want.
I created a minimalist example to show you what i am trying to do.
using System;
namespace example
{
public class Car
{
public int Price;
public string ModelName;
public Boolean Sold;
public delegate void SellEventHandler(string str);
public static event SellEventHandler _OnSell;
public void OnSell(string str)
{
Console.WriteLine("event was fired");
}
public Car(int price, string modelname)
{
Price = price;
ModelName = modelname;
Sold = false;
_OnSell = OnSell;
}
}
public class Program
{
static void Main()
{
Car _car = new Car(6000, "audi");
_car._OnSell += Car_OnSell;
}
public void Car_OnSell(string message)
{
Console.WriteLine(message);
}
}
}
Even though i haven't implemented when the event will be invoked ( it should be invoked when the Sold property of the _car changes ), i want to execute the OnSell(string str) method of the Car class ( prints "event was fired" ) and after that, i want to execute the Car_OnSell function ( see code _car.OnSell += Car_OnSell )
Hopefully you get the idea of what i am trying to do here. Right now the error i get is Member 'example.Car._OnSell' cannot be accessed with an instance reference; qualify it with a type name instead on the line _car.OnSell += Car_OnSell;. However i am not sure if i am going in the right direction with this at all.
I think I understand what you're doing, and here's how I would do it.
Don't hook up an event in your class. Instead, create a 'Sell' method that does whatever the class would normally do (like set Sold == true), but first check if the client hooked up your _OnSell event, and fire that first. You may want to provide some way for the client to cancel the sale in the _OnSell event as well.
You also need to make your Car_OnSell static, since you're hooking it up from a static method (Main). This is because a non-static method requires a class instance to access it.
Here's an example:
static void Main()
{
var car = new Car(6000, "audi");
car._OnSell += Car_OnSell;
car.Sell(string.Format("Selling the car: {0}", car.ModelName));
}
public static void Car_OnSell(string message)
{
Console.WriteLine(message);
}
public class Car
{
public int Price { get; set; }
public string ModelName { get; set; }
public Boolean Sold { get; set; }
public delegate void SellEventHandler(string str);
public event SellEventHandler _OnSell;
public void Sell(string str)
{
if (_OnSell != null)
{
_OnSell(str);
}
this.Sold = true;
}
public Car(int price, string modelname)
{
Price = price;
ModelName = modelname;
Sold = false;
}
}

C# add const field using attributes

We have class 'SomeClass':
namespace Namespace
{
class SomeClass
{
// something
}
}
And attribute 'SomeAttribute':
class SomeAttribute : System.Attribute { }
Task: add to all classes market by SomeAttribute 'public const string Type' field. Modified classes must be following:
class SomeClass
{
// something
public const string Type = #"Namespace.SomeClass";
}
UPD:
I'm using following approach for message transaction:
class Manager
{
// message has 3 parts:
// string message = String.Format("{0}{1}{2}",
// typeof(SomeClass).ToString(),
// splitter,
// Manager.Serialize(someClassObj)
// )
public static string GetType(string message) { /* some code */ }
public static string Serialize(SomeClass message) { /* XML serialization */ }
public static SomeClass Deserialize(string message) { /* deserialization */ }
}
class Logic
{
public void ProcessMessage(string message)
{
switch (Manager.GetType(message))
{
case SomeClass.Type:
{
SomeClass msg = Manager.Deserialize(message) as SomeClass;
// send message to binded objects
}
break;
case ClassInheritedFromSomeClass.Type:
{
// the same
}
break;
// etc.
}
}
}
UPD 2:
More about messages. At this time I'm using next approach:
public class BaseMessage
{
public const string Type = #"Messages.BaseMessage";
}
public class LoginMessage : BaseMessage
{
public new const string Type = #"Messages.Client.LoginMessage";
public string Nickname { get; set; }
public string Password { get; set; }
}
Conclusion
I think best case is to modify Manger like this:
class Manager
{
// create event table
public Action<BaseMessage> this[string eventName]
{
get
{
if (!m_eventTable.ContainsKey(eventName))
{
m_eventTable.Add(eventName, new Action<BaseMessage>(message => { }));
}
return m_eventTable[eventName];
}
set
{
m_eventTable[eventName] = value;
}
}
public void Send(BaseMessage message, string messageName)
{
if (m_eventTable.ContainsKey(messageName) && this[messageName].Method != null)
{
this[messageName].Invoke(message);
}
}
private Dictionary<string, Action<BaseMessage>> m_eventTable = new Dictionary<string, Action<BaseMessage>>();
}
Using switch with GetType is the wrong way to implement polymorphism, because it only checks the most-derived class (breaks extensibility).
In your particular case, where you want the Manager to be responsible for the behavior, you might use the dynamic keyword and overloaded methods. But this will again violate SOLID, because it isn't open for extension.
Instead of violating SOLID this way, try to find a way to use virtual methods to perform the type-specific action.

Categories