2

I would like to create a method that can return the generic type defined in the class, here is a detailed example;

https://dotnetfiddle.net/SApVp3

using System;
                    
public class Program
{
    public static void Main()
    {
        // This would be some string imported from a CSV file   
        var customerData = "Customer,1,Ford";       
        var personData = "Person,675,Henry,Ford";
        
        var customerImporter = new ImportData<CompanyMaster>();
        customerImporter.ImportDataFromFile(customerData);
                                      
        var personImporter = new ImportData<PersonMaster>();
        personImporter.ImportDataFromFile(personData);
    }
}

public class GenericRepository<TBase> 
    where TBase : EntityBase
{
    public void Insert(TBase entity)
    {
        //.. generic Insert to database
    }
}

public class ImportData<TBase>  
    where TBase : EntityBase
{
    GenericRepository<TBase> _genericRepository;
    
    //ctor
    public void ImportDataFromFile(string data)
    {
        // convert the string data to TBase
        _genericRepository = new GenericRepository<TBase>();
    }
}

public class CsvConverter<TBase> where TBase: EntityBase{
    
    public TBase ConvertTo(string someString)
    {
        if (someString.StartsWith("Customer"))
        {
            return GetCompany(someString);
        } 
        
        else return GetPerson(someString);
    }
    
    private CompanyMaster GetCompany(string companyString){
        return new CompanyMaster();
    }
    
    private PersonMaster GetPerson(string companyString){
        return new PersonMaster();
    }

}


public abstract class EntityBase
{
    public int Id { get; set; }
    public DateTime CreatedDate { get; set; }
}

public class CompanyMaster : EntityBase
{
    public string CompanyName { get; set; }
}

public class PersonMaster : EntityBase
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

This currently throws;

Compilation error (line 47, col 11): Cannot implicitly convert type 'CompanyMaster' to 'TBase' Compilation error (line 50, col 15): Cannot implicitly convert type 'PersonMaster' to 'TBase'

Can this be made to work?

Mark Cooper
  • 6,738
  • 5
  • 54
  • 92
  • 2
    "*as this derives from TBase, why can't this be returned?*" -- `TBase` might be another subclass of `AbstractClass`, which isn't `DerivedClass`. E.g. I might be calling `GenericLogicClass.GetRecord()` – canton7 Jan 15 '21 at 13:52
  • 1
    The way to make this work is either to constrain `where TBase : DerivedClass`, or do `public DerivedClass GetRecord()`, and I suspect you want to do neither. Which means you need to take a hard look at what you're *trying* to achieve – canton7 Jan 15 '21 at 13:56
  • See @canton7 comments. If you want really do that, try `return record as TBase;` – vernou Jan 15 '21 at 13:58
  • 1
    ^ (which will return `null` any time that `TBase` is something different to `DerivedClass`, which probably isn't what you want either) – canton7 Jan 15 '21 at 13:58
  • @canton7 I have a generic repository where every entity derives from `AbstractBase`, and I am trying to create some generic methods for importing data from CSV files create and sending these to the repository. – Mark Cooper Jan 15 '21 at 14:00
  • @Vernou If I cast to the base type I'll lose any properties in the derived type, so this won't suit my needs. – Mark Cooper Jan 15 '21 at 14:01
  • "*where every entity implements TBase*" -- do you mean, where every entity derives from `AbstractClass`? `TBase` is a generic type parameter, which can take *any* value, provided that the value ultimately derives from `AbstractClass` – canton7 Jan 15 '21 at 14:01
  • Simply put, it seems you want the operation `new GenericLogicClass().GetRecord()` to return something which is a `MyCustomDerivesClass` (because you said it returns `TBase`, and `TBase` takes the value `MyCustomDerivedClass` here) **and also** something which a `DerivedClass` (because you're trying to return an instance of `DerivedClass`). The whole concept makes no sense when you stop to think about it – canton7 Jan 15 '21 at 14:03
  • @canton7 yes I meant every class derives from `AbstractBase`. In my case I have an class called `EntityBase` which has fields like `Id`, `CreatedDate` etc so these are common across all my entities. Then I have specific classes for each entity which contain the specific properties and relationships of the class (for example `Name`, `Colour` etc). All the generic repository code is working, just hit a road block with this pattern. – Mark Cooper Jan 15 '21 at 14:12
  • 2
    It's still not clear what you're trying to do with `GetRecord` -- should this return `TBase` (whatever that happens to be -- it can be *any* type which derives from `AbstractBase`), or should it return a `DerivedClass`? It has to be one or the other. – canton7 Jan 15 '21 at 14:14
  • @canton7 I think by trying to make this a simple repo, I've confused the intention. Here is a more detailed example https://dotnetfiddle.net/SApVp3 – Mark Cooper Jan 15 '21 at 14:54
  • 1
    @OlivierRogier thanks for the feedback. I have created a fiddle with the exact problem, and updated the question. – Mark Cooper Jan 15 '21 at 14:56
  • You are not using CsvConverter class in dotnetfiddle. Could you use it to make your code's intentions more clear? – Andriy Kozachuk Jan 15 '21 at 15:13
  • 1
    @AndriyKozachuk Updated fiddle, and also created a new one with answer from OlivierRogier. https://dotnetfiddle.net/ct8w0a – Mark Cooper Jan 15 '21 at 15:24

1 Answers1

1

You need to do an upcast using:

public TBase ConvertTo(string someString)
{
  if ( someString.StartsWith("Customer") )
  {
    return (TBase)Convert.ChangeType(GetCompany(someString), typeof(TBase));
  }
  else
  {
    return (TBase)Convert.ChangeType(GetPerson(someString), typeof(TBase));
  }
}

Or as suggested by @canton7:

if ( someString.StartsWith("Customer") )
{
  return (TBase)(object)GetCompany(someString);
}
else
{
  return (TBase)(object)GetPerson(someString);
}

Difference between casting and using the Convert.To() method

  • This will throw an `InvalidCastException`, because `DerivedClass` does not implement `IConvertible` – canton7 Jan 15 '21 at 14:08
  • 2
    https://dotnetfiddle.net/MVvaNd -- see the exception in the window at the bottom – canton7 Jan 15 '21 at 14:11
  • If you want to write something which returns an instance of `DerivedClass` when `TBase` is `DerivedClass` and throws an exception otherwise, there are cheaper and more idiomatic ways to do this, such as `(TBase)(object)new DerivedClass()`. This is incredibly unlikely to be what OP wants however – canton7 Jan 15 '21 at 14:13
  • It works with `GenericLogicClass`, obviously, but not with **any other type which extends `AbstractBase`**. See the link I posted for example. Like I said in my previous comments, if you want something which **only** works when the user uses `GenericLogicClass`, and throws an exception for `GenericLogicClass`, there are easier ways to write this. It's also surely not what OP wants, so saying they "need to" do this is very misleading. – canton7 Jan 15 '21 at 14:16
  • I still think it's a very misleading answer, and incredibly unlikely to be what the OP wants (see the ongoing discussions in the comments). My point about there being a better way to write this (very silly thing) also still stands. – canton7 Jan 15 '21 at 14:20
  • 1
    I've updated the question with a working fiddle which more closely matches my needs. – Mark Cooper Jan 15 '21 at 14:57
  • 1
    @OlivierRogier yes this works! Thanks so much, updated fiddle here: https://dotnetfiddle.net/ct8w0a. Upvoted - sorry wasn't me that downvoted, so its back to zero :-( – Mark Cooper Jan 15 '21 at 15:22
  • @MarkCooper Don't use Convert.ChangeType there -- it's entirely pointless, as you're not actually using `IConvertible`. You're simply abusing that method: you can do just the same by passing it to *any* method which returns that object cast to `object`. Just cast via object directly. – canton7 Jan 15 '21 at 15:45