Generic conversion issue - c#

I'm trying to design a pattern to orchest several operations. Each operation would take a parameter and deliver a result. That result might or might not be used by the following operation. This is a simplified version of the design, but if you copy/paste this on a console projecto it will "work" (there's a compiling error I can't get fixed).
Error
The type
'ConsoleApplication1.InternalDebit'
cannot be used as type parameter 'T1' in the generic type or method
'ConsoleApplication1.Orchestrator.Add(T1)'. There is no implicit
reference conversion from
'ConsoleApplication1.InternalDebit'
to
'ConsoleApplication1.Operation'. c:\projects\BCP\BaseMvc\ConsoleApplication1\ConsoleApplication1\Program.cs 17 13 ConsoleApplication1
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var internalDebit = new InternalDebit<InternalDebitParameter, InterbankCreditParameter>(new InternalDebitParameter() { Id = 1 });
var orchestrator = new Orchestrator();
// error here!
orchestrator.Add(internalDebit);
}
}
public interface IParameter
{
}
public interface IResult
{
}
public interface IReversible
{
void Reverse();
}
public interface IOperation<T, R>
where T : class, IParameter
where R : class, IResult
{
Type ParameterType { get; }
Type ResultType { get; }
T Parameter { get; set; }
R Execute(T parameter);
}
public abstract class Operation<T, R> : IOperation<T, R>
where T : class, IParameter
where R : class, IResult
{
public virtual R Execute(T parameter)
{
this.Parameter = parameter;
return default(R);
}
public Type ParameterType
{
get { return typeof(T); }
}
public Type ResultType
{
get { return typeof(R); }
}
public T Parameter { get; set; }
public Operation(T parameter)
{
this.Parameter = parameter;
}
}
public class InternalDebitParameter : IParameter
{
public int Id { get; set; }
}
public class InterbankCreditParameter : IParameter, IResult
{
public int Id { get; set; }
}
public class InternalDebit<T, R> : Operation<T, R>
where T : class, IParameter
where R : class, IResult
{
public InternalDebit(T parameter)
: base(parameter)
{
}
public override R Execute(T parameter)
{
return new InterbankCreditParameter() { Id = 2 } as R;
}
}
public class Orchestrator
{
public List<Operation<IParameter, IResult>> Operations { get; private set; }
public List<IParameter> Parameters { get; private set; }
public void Add<T1>(T1 t) where T1 : Operation<IParameter, IResult>
{
this.Operations.Add(t);
}
public void SetUpParameters(params IParameter[] parameters)
{
this.Parameters = new List<IParameter>();
parameters.ToList().ForEach(s => this.Parameters.Add(s));
}
public void Play()
{
IParameter generalResult = null;
foreach (var instrument in this.Operations)
{
var parameter = this.Parameters.FirstOrDefault(s => s.GetType() == instrument.ParameterType);
if (parameter == null)
{
IResult actualResult = null;
if (generalResult != null)
{
try
{
actualResult = instrument.Execute(generalResult);
}
catch (Exception ex)
{
if (instrument is IReversible)
((IReversible)instrument).Reverse();
else
throw;
break;
}
finally
{
if (actualResult is IParameter)
generalResult = (IParameter)actualResult;
}
}
else
{
throw new Exception("Orchetrator missconfiguration.");
}
}
}
}
}
}

If you play a little with covariance/contravariance you may be able to do something similar to what you're after. Or anyway, the compiler will tell you more precisely where what you're trying to do is not type-safe.
First step: the error you're getting states that There is no implicit reference conversion from 'InternalDebit<InternalDebitParameter,InterbankCreditParameter>' to 'Operation<IParameter,IResult>'.
So, since InternalDebit implements IOperation, the first thing you can do is make IOperation covariant, trying to define it as:
public interface IOperation<out T, out R>
This would mean that a variable of type IOperation<IParameter,IResult> would happily accept a value of type Operation<InternalDebitParameter,InterbankCreditParameter>, which is one step closer to what you want.
You would then have your Add's method signature constrained in terms of IOperation instead of Operation
public void Add<T1>(T1 t) where T1 : IOperation<IParameter, IResult>
The compiler tells us something's wrong:
Invalid variance: The type parameter 'T' must be invariantly valid on 'IOperation<T,R>.Parameter'. 'T' is covariant.
Invalid variance: The type parameter 'T' must be contravariantly valid on 'IOperation<T,R>.Execute(T)'. 'T' is covariant.
That's the first indication of why this code is unsound. Covariant parameters can only be used "on the way out" of function (i.e. as a return type), not as "in" parameters.
Second step making IOperation covariant. This may be painful and change your code, as it means changing Execute not to accept parameters of type T.
public interface IOperation<out T, out R>
where T : class, IParameter
where R : class, IResult
{
Type ParameterType { get; }
Type ResultType { get; }
T Parameter { get; /*set;*/ } //can't allow the interface to set T
// R Execute(T parameter); // can't have an Execute with T as a parameter
R Execute(); // you can however inject T in the constructor of the
// inherited class and call Execute without parameters
}
Third step you now get a new error:
The best overloaded method match for 'System.Collections.Generic.List<Operation<IParameter,IResult>>.Add(Operation<IParameter,IResult>)' has some invalid arguments
This is again a covariance issue. List is not covariant and you can't Add t to a List.
I don't really know what to suggest,since I don't want to change completely the intent of your code (especially since I can't say I fully understand it...)
You may find something useful in this answer, for instance:
Covariance and IList

You're taking generics too far into C++ templating power. On the line that gives the error you're implicitly creating the function:
public void Add(InternalDebit<InternalDebitParameter, InterbankCreditParameter>);
As declared, this class inherits from:
Operation<InternalDebitParameter, InterbankCreditParameter>
The generic requirement howeveer states that T1 should be of type Operation<IParameter, IResult>, which it isn't, even though both parameters do inherit from the correct types, since there is no polymorphism allowed.
What you're trying to achieve here is inherently impossible with generics (or templates in C++ actually) because you are specifying way too much, and specifying inheritance requirements that can never be satisfied. You need to remember that generics are in a way just a luxury shorthand of writing many classes with only a little bit of code, they do not introduce recursive polymorphism all of a sudden.
Long story short, rewrite the code to use inheritance and base classes rather than depending on generics. I suspect your entire pattern is possible without a single generic and just as type safe.

Ok, for the sake of completeness of this post, I'll show you how I finally get this working.
It can be better, I'm still open to suggestions. Unfortunatelly I got to move on from this task, it's already delayed.
I'll post and edit to this answer in order to follow up it on Code Review site.
Copy/Paste in a console application, it's a fully functional code example.
class Program
{
static void Main(string[] args)
{
var transferenceInfo = new InterbankTranferenceInfo();
var orchestrator = new Orchestrator(new InternalDebitOperation(transferenceInfo),
new InterbankCreditOperation(),
new CommissionOperation());
orchestrator.Run();
}
}
public class InterbankTranferenceInfo : IParameter
{
public bool InternalDebitDone { get; set; }
public bool InterbankCreditDone { get; set; }
public bool CommissionDone { get; set; }
}
public class InternalDebitOperation : Operation<InterbankTranferenceInfo>, IOperation<InterbankTranferenceInfo>
{
public InternalDebitOperation(InterbankTranferenceInfo parameter)
: base(parameter)
{
}
public override InterbankTranferenceInfo Execute()
{
return new InterbankTranferenceInfo() { InternalDebitDone = true };
}
}
public class InterbankCreditOperation : Operation<InterbankTranferenceInfo>, IOperation<InterbankTranferenceInfo>
{
public override InterbankTranferenceInfo Execute()
{
Parameter.InterbankCreditDone = true;
return Parameter;
}
}
public class CommissionOperation : Operation<InterbankTranferenceInfo>, IReversible, IOperation<InterbankTranferenceInfo>
{
public override InterbankTranferenceInfo Execute()
{
Parameter.CommissionDone = true;
// Uncomment this code to test Reverse operation.
// throw new Exception("Test exception, it should trigger Reverse() method.");
return Parameter;
}
public void Reverse()
{
Parameter.CommissionDone = false;
}
}
public enum OperationStatus
{
Done,
Pending,
Reversed
}
public interface IParameter
{
}
public interface IReversible
{
void Reverse();
}
public interface IOperation<out T> : IInternalOperation<T> where T : IParameter
{
}
public interface IInternalOperation<out T> : IExecutableOperation<T>
{
bool GetParameterFromParentOperation { get; }
OperationStatus Status { get; set; }
IParameter Execute(IParameter parameter);
}
public interface IExecutableOperation<out T>
{
T Execute();
}
//[System.Diagnostics.DebuggerStepThroughAttribute()]
public abstract class Operation<T> : IInternalOperation<T> where T : IParameter
{
public T Parameter { get; private set; }
public bool GetParameterFromParentOperation { get { return this.Parameter == null; } }
public OperationStatus Status { get; set; }
public Operation()
{
Status = OperationStatus.Pending;
}
public Operation(IParameter parameter)
{
Status = OperationStatus.Pending;
this.Parameter = (T)parameter;
}
public abstract T Execute();
public virtual IParameter Execute(IParameter parameter)
{
this.Parameter = (T)parameter;
return this.Execute();
}
}
public class Orchestrator
{
public List<IOperation<IParameter>> Operations { get; private set; }
public Orchestrator(params IOperation<IParameter>[] operations)
{
this.Operations = new List<IOperation<IParameter>>();
foreach (var item in operations)
{
this.Operations.Add((IOperation<IParameter>)item);
}
}
public IParameter Run()
{
IParameter previousOperationResult = null;
foreach (var operation in this.Operations)
{
try
{
if (operation.GetParameterFromParentOperation)
previousOperationResult = operation.Execute(previousOperationResult);
else
previousOperationResult = operation.Execute();
operation.Status = OperationStatus.Done;
}
catch (Exception)
{
foreach (var o in this.Operations)
{
if (o is IReversible)
{
((IReversible)o).Reverse();
o.Status = OperationStatus.Reversed;
}
else
throw;
}
break;
}
}
return previousOperationResult;
}
}
EDIT
Code Review Post

Related

Generic Factory Method Casting Issue

Trying to create a factory to return a generic interface (following this answer) but getting the error:
Can't implicitly convert IFinancialsSyncService<Vendor, QuickBooksVendor> to IFinancialsSyncService<TEntity, TQuickBooksEntity>. Anexplicit conversion exists, are you missing a cast?
public class QuickBooksEntityServiceFactory
{
public IFinancialsSyncService<TEntity, TQuickBooksEntity> Create<TEntity, TQuickBooksEntity>()
where TEntity : class, IEntity, IFinancials, new()
where TQuickBooksEntity : class, IQuickBooksEntity
{
if (typeof(TEntity) == typeof(QuickBooksVendor))
{
return new QuickbooksVendorService();
}
throw new InvalidOperationException();
}
}
The service confirms to the IFinancialsSyncService interface:
public class QuickbooksVendorService : IFinancialsSyncService<Vendor, QuickBooksVendor>
However, if I cast it explicitly, I get a Cast is redundant error along with the first error still.
return (IFinancialsSyncService<Vendor, QuickBooksVendor>)new QuickbooksVendorService();
So the error is confusing me. What am I doing wrong?
UPDATE
This is what I'm trying to simplify. There are several instances similar to this also that call other common methods of the interface.
switch (enumDataElement)
{
//Export jobs
case DataElement.Item:
var itemService = new QuickbooksItemService();
exportResult = itemService.UpdateMozzoEntityWithFinancialsId(session, response, EntityId, intUserId);
break;
case DataElement.Vendor:
var VendorService = new QuickbooksVendorService();
exportResult = UpdateMozzoEntityWithFinancialsId(new QuickbooksVendorService(),session, response, EntityId, intUserId);
break;
case DataElement.Bill:
var billService = new QuickbooksBillService();
exportResult = billService.UpdateMozzoEntityWithFinancialsId(session, response, intUserId);
break;
case DataElement.PurchaseOrder:
var qbPOService = new QuickbooksPurchaseOrderService();
exportResult = qbPOService.UpdateMozzoEntityWithFinancialsId(session, response, intUserId);
break;
case DataElement.SalesReceipt:
var salesReceiptService = new QuickbooksSalesReceiptService();
exportResult = salesReceiptService.UpdateStratusEntityWithFinancialsId(session, response, intUserId);
break;
}
And replace it with something like:
var qbEntityService = EntityServiceFactory.Create(enumDataElement);
What would this factory look like?
This has to do with Liskov's Substitution Principle. Imagine that your Generic type is instead a property of the interface:
public interface IFinancials { }
public interface IFinancialsSyncService
{
IFinancials Financials { get; set; }
}
Now we implement this interfaces:
public class Financials : IFinancials {}
public class FinancialsSyncService : IFinancialSyncService
{
public Financials Financials { get; set; }
}
This results in a compiler error:
Compilation error: 'Program.FinancialsSyncService' does not implement interface member 'Program.IFinancialsSyncService.Financials'. 'Program.FinancialsSyncService.Financials' cannot implement 'Program.IFinancialsSyncService.Financials' because it does not have the matching return type of 'Program.IFinancials'.
Both problems have the same issue. In my example, the interface states that the result is of type IFinancials but is a more specific derived type Financials and even though any valid value that is placed in the property fulfills the interface, it cannot be replaced with any value derived from IFinancials only types that derive from Financials.
So if your code looked like:
public interface IFinancialsSyncService<TEntity>
where TEntity : IEntity
{
TEntity Financials { get; set; }
}
and you create a class:
public class QuickbooksVendorService : IFinancialSyncService<Vendor>
{
public Vendor Financials { get; set; }
}
However, now QuickbooksVendorService is a IFinancialSyncService<Vendor> not a IFinancialSyncService<TEntity> because the property is the derived type. Even if you didn't have this specific property it still leads to the same problem that generic type is more specific than the interface.
use Factory method and Adapter pattern
[TestFixture]
public class Class1
{
[Test]
public void Go()
{
var qbItem = Export(1);
var qbVendor= Export(2);
var qbSales= Export(3);
}
public qbEntityService Export(int number)
{
var qb = Class1.Create(number);
return qb.UpdateMozzoEntityWithFinancialsId();
}
public static IEntityService Create(int enumDataElement)
{
switch (enumDataElement)
{
case 1:
return new QuickbooksItemService();
case 2:
return new QuickbooksVendorService();
case 3:
return new QuickbooksSalesReceiptServiceAdapter(new QuickbooksSalesReceiptService());
default:
throw new Exception();
}
}
}
public interface IEntityService
{
qbEntityService UpdateMozzoEntityWithFinancialsId();
}
public class qbEntityService
{
}
public class QuickbooksItemService : IEntityService
{
public qbEntityService UpdateMozzoEntityWithFinancialsId()
{
Console.WriteLine("I am QuickbooksItemService, performing UpdateMozzoEntityWithFinancialsId");
return new qbEntityService();
}
}
public class QuickbooksVendorService : IEntityService
{
public qbEntityService UpdateMozzoEntityWithFinancialsId()
{
Console.WriteLine("I am QuickbooksVendorService, performing UpdateMozzoEntityWithFinancialsId");
return new qbEntityService();
}
}
public class QuickbooksSalesReceiptService
{
public qbEntityService UpdateStratusEntityWithFinancialsId()
{
Console.WriteLine("I am QuickbooksSalesReceiptService, performing UpdateStratusEntityWithFinancialsId");
return new qbEntityService();
}
}
public class QuickbooksSalesReceiptServiceAdapter : IEntityService
{
private QuickbooksSalesReceiptService adaptee;
public QuickbooksSalesReceiptServiceAdapter(QuickbooksSalesReceiptService adaptee)
{
this.adaptee = adaptee;
}
public qbEntityService UpdateMozzoEntityWithFinancialsId()
{
return adaptee.UpdateStratusEntityWithFinancialsId();
}
}

Why do I get NullReferenceException in my factory pattern implementation, c#?

Main class:
public class ClP_Login
{
private Form vrcView;
private I_Repository<I_Identifiable> vrcRepository = null;
public ClP_Login(Form vrpView)
{
vrcView = vrpView;
SetTheme();
}
private void SetTheme()
{
if(vrcView !=null)
vrcView.BackColor = Cl_BaseColor.StandardBackground;
}
public void CreateNewUser()
{
ClE_User test = new ClE_User();
test.Name = "test name";
test.Password = "";
Cl_RepositoryFactory vrlFactory = new Cl_RepositoryFactory();
vrcRepository = vrlFactory.CreateRepository(E_Repositories.User);
vrcRepository.Add(test);
}
}
Cl_RepositoryFactory class:
public class Cl_RepositoryFactory
{
public virtual I_Repository<I_Identifiable> CreateRepository(E_Repositories vrpRepository)
{
I_Repository<I_Identifiable> vrlRepository = null;
switch (vrpRepository)
{
case E_Repositories.User:
vrlRepository = new Cl_UserRepository() as I_Repository<I_Identifiable>;
break;
}
return vrlRepository;
}
}
Enum E_Repositories:
public enum E_Repositories
{
User
}
I_Identifiable Interface:
public interface I_Identifiable
{
int Id { get; set; }
}
I_Repository Interface:
public interface I_Repository<T>
{
T GetById(Guid id);
T GetByQuery(Queue query);
void Add(T item);
void Remove(T item);
void Update(T item);
}
Cl_UserRepository class:
public class Cl_UserRepository : I_Repository<ClE_User>
{
public void Add(ClE_User item)
{
MessageBox.Show("Created new User");
}
public ClE_User GetById(Guid id)
{
throw new NotImplementedException();
}
public ClE_User GetByQuery(Queue query)
{
throw new NotImplementedException();
}
public void Remove(ClE_User item)
{
throw new NotImplementedException();
}
public void Update(ClE_User item)
{
throw new NotImplementedException();
}
}
And ClE_User class:
public class ClE_User : I_Identifiable
{
public int Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
}
The question is, why do I get null reference exception using vrcRepository?
vrlFactory.CreateRepository(E_Repositories.User); return null and I don't have any idea why, please help
In CreateRepository method try to remove casting statement as I_Repository<I_Identifiable>. If your code will not compile, that will mean Cl_UserRepository is not compatible with I_Repository<I_Identifiable>.
Otherwise everyting is correct with CreateRepository method
ClE_User inherits from I_Identifiable, but I_Repository<ClE_User> does not inherit from I_Repository<I_Identifiable>. Those are different interfaces as far as C# is concerned.
To elaborate more, you have I_Repository<I_Identifiable> vrcRepository which should in theory take I_Repository of any I_Identifiable kind. So let's say you initialize this member to some other, for instance I_Repository<ClE_SomethingOtherThanUser>. But then you call vrcRepository.Add(test). That's not going to work, with test being ClE_User.
Now, first remove the as I_Repository<I_Identifiable> part, and then to make it compile make I_Repository just a plain dumb non-generic interface, whose methods take I_Identifiable parameter or return I_Identifiable value. This may not be what you wanted, but it will compile.
EDIT
I realize that the enum will trigger. You are right
new Cl_UserRepository() as I_Repository
CL_UserRepository has to implement the interface you are trying to return, and then you don't need to type cast it at all. Sorry! I owe you a case of beer.

Generic container: why do I need an interface?

Say I have the following
public interface IInterval<T>
{
T Start { get; }
T Stop { get; }
}
public class DateTimeInterval : IInterval<DateTime>
{
private DateTime _start;
private DateTime _stop;
public DateTimeInterval(DateTime start, DateTime stop)
{
_start = start; _stop = stop;
}
public DateTime Start
{
get { return _start; }
}
public DateTime Stop
{
get { return _stop; }
}
}
public class SortedIntervalList<T>
where T : IInterval<T>, IComparable<T>
{
}
If I were to now try to instantiate the container
var test = new SortedIntervalList<DateTimeInterval>();
I get a compilation error
The type 'Test' cannot be used as type parameter 'T' in the generic
type or method TestContainer<T>. There is no implicit reference
conversion from 'Test' to ITest<Test>.
Why is this?
Note on edit history
For clarity, classes for the original question are included below
public interface ITest<T>
{
int TestMethod();
}
public class Test : ITest<bool>
{
public int TestMethod()
{
throw new NotImplementedException();
}
}
public class TestContainer<T>
where T : ITest<T>
{
}
where T : ITest<T>
Your class inherits ITest<bool>, which is not ITest<T> for your T (Test).
As the error is trying to tell you, that does not meet your generic constraint, so you can't do that.
Because you expect your T in TestContainer<T> to be ITest<T>. that doesn't make sense. I think you meant :
public class TestContainer<C, T>
where C : ITest<T>
{
}
For your updated code in your question:
public class SortedIntervalList<C, T>
where C : IInterval<T>, IComparable<T>
{ }
With:
test = new SortedIntervalList<DateTimeInterval, DateTime>();

Generic interface compiles but does not work at runtime

I have an issue with generic interface. The compiler does not give any compiling errors but at run-time unseen exception is thrown.
public interface IStructure
{
string Name {get;}
}
public interface IStructureNavigation<T> : IStructure where T : IStructure
{
T Parrent {get;}
}
public class ResourceStructure : IStructureNavigation<ResourceStructure>
{
private ResourceStructure _parrent;
public virtual string Name
{
get;
set;
}
public virtual ResourceStructure Parrent
{
get { return _parrent; }
}
}
Can someone explain why does the following code fail at runtime?
public class Action
{
private ObjectContext _context;
private ObjectSet<ResourceStructure> _structue;
private IQueryable<ResourceStructure > _parrents;
public Action()
{
string connectionString =
ConfigurationManager
.ConnectionStrings["Structure"].ConnectionString;
_context = new ObjectContext(connectionString);
_context.ContextOptions.LazyLoadingEnabled = true;
_structue = _context.CreateObjectSet<ResourceStructure>();
_parrents = _structue.Where(x => x.ParentID == null);
// FAILS IN FOREACH LOOP : UNSEEN EXCPTION
foreach (IStructureNavigation<IStructure> strt in _parrents)
{
//do something
}
//WORKS IF USING CONCRETE TYPE NOT INTERFACE
foreach(IStructureNavigation<ResourceStructure > strt in _parrents)
{
//do something
}
}
}
Declare T as covariant
public interface IStructureNavigation<out T> : IStructure where T : IStructure
That's because your instance is of type IStructureNavigator<ResourceStructure> and not IStructureNavigator<IStructure>.
If you need to use the interface, you can use the Cast extension method:
_parrents = _context.CreateObjectSet<ResourceStructure>().Cast<IStructure>();
Which version of the Framework are you using?

Override Property with different compatible Type

I need a base class with a property where I can derive classes with the same property but different (compatible) types. The base Class can be abstract.
public class Base
{
public virtual object prop { get; set; }
}
public class StrBase : Base
{
public override string prop { get; set; } // compiler error
}
public class UseIt
{
public void use()
{
List<Base> l = new List<Base>();
//...
}
}
I tried it with Generics but that gives me a problem when using the class, because I want to store differently typed base classes in the List.
public class BaseG<T>
{
public T prop { get; set; }
}
public class UseIt
{
public void use()
{
List<BaseG> l = new List<BaseG>(); // requires type argument
//...
}
}
Here's an alternative approach to proposed solution:
public abstract class Base
{
public abstract void Use();
public abstract object GetProp();
}
public abstract class GenericBase<T> : Base
{
public T Prop { get; set; }
public override object GetProp()
{
return Prop;
}
}
public class StrBase : GenericBase<string>
{
public override void Use()
{
Console.WriteLine("Using string: {0}", Prop);
}
}
public class IntBase : GenericBase<int>
{
public override void Use()
{
Console.WriteLine("Using int: {0}", Prop);
}
}
Basically I've added a generic class in the middle that stores your properly-typed property. this will work assuming that you never need to access Prop from the code that iterates the members of the List<Base>. (You could always add an abstract method to Base called GetProp that casts the generic to an object if that's required.)
Sample usage:
class Program
{
static void Main(string[] args)
{
List<Base> l = new List<Base>();
l.Add(new StrBase {Prop = "foo"});
l.Add(new IntBase {Prop = 42});
Console.WriteLine("Using each item");
foreach (var o in l)
{
o.Use();
}
Console.WriteLine("Done");
Console.ReadKey();
}
}
Edit: Added the GetProp() method to illustrate how the property can be directly accessed from the base class.
You can't override the type of a property. Take a look at the following code:
StrBase s = new StrBase();
Base b = s;
This is completely valid code. But what happens when you try to do this?
b.prop = 5;
The integer can be converted to object, because everything is derived from object. But since b is actually a StrBase instance, it would have to convert the integer to a string somehow, which it can't. So that is why you aren't allowed to override the type.
The same principle applies to generics:
List<BaseG<object>> l = new List<BaseG<object>>();
BaseG<string> s = new BaseG<string>();
// The compiler will not allow this.
l.add(s);
// Here's the same problem, convert integer to string?
BaseG<object> o = l[0];
o.prop = 5;
This is because generic types in C# 2.0 are invariant. C# 4.0 does allow this type of conversions, called covariance and contravariance.
Solutions
An option is to cast the object back to string when you need it. You could add type validation in the subclass:
public class StrBase : Base
{
private string propValue;
public override object prop {
get
{
return this.propValue;
}
set
{
if (value is string)
{
this.propValue = (string)value;
}
}
}
}
You could also expose a type-safe property in the subclass:
public class StrBase : Base
{
public string strProp {
get
{
return (string)this.prop;
}
set
{
this.prop = value;
}
}
}
This is possible since C# 9.0
Beginning with C# 9.0, override methods support covariant return types.
(see Microsoft docs)
public class First
{
private int someV;
public virtual object SomeV { get => someV; set => someV = (int)value; }
public First() { }
}
public class Two : First
{
private string someV;
public override object SomeV { get => someV; set => someV = value.ToString(); }
public Two() { }
}
and use of those:
First firstClass = new First();
firstClass.SomeV = 1;
Two twoClass = new Two();
twoClass.SomeV = "abcd";

Categories