1

I want to know how to get access to an extension method from within a base class by passing the generic type.

I have several different classes all containing ToModel and ToContract Funcs. These methods are to switch from Entity Framework type to Data Contracts and vice versa.

They all do the same repetitive calls so I would like to condense down the code.

I have tried invoking functions using reflection. And many other methods to none avail. I have simplified the problem that I am facing in the following code.

My problem is that I cannot access the extension method from within the base class. Please help.

Error I am receiving: Class Does not contain a definition for 'ToModel' method.

Simplified Code

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestExtensions
{    
    public class Program : Base<Client>
    {
        static void Main(string[] args)
        {
            Client c = new Client();
            c.FirstName = "First Name";

            Console.WriteLine(c.FirstName);

            c.ToModel();

            Console.WriteLine(c.FirstName);

            Program p = new Program();
            p.go(c);
        }

        public void go(Client c)
        {
            base.ChangeNameAgain(c);
            Console.WriteLine(c.FirstName);

        }
    }
}

Extension Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestExtensions
{
    public static class ExtensionClass
    {
        public static Client ToModel(this Client c)
        {
            c.FirstName = "First Name Changed";

            return c;
        }
    }
}

Client Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestExtensions
{
    public class Client
    {
        public string FirstName { get; set; }
    }
}

Base Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestExtensions
{
    public class Base<T>
    {
        public T ChangeNameAgain(T c)
        {
            // This is where I need help
            // Need to invoke the ToModel Method using reflection               


            return (T) c;
        }

    }
}

My actual code

Base

public class BaseApi<TContractType, TModelType> : ApiController
    where TModelType : DataModelBase, IApiExtensionModel, new()
    where TContractType: DataContractBase, IApiExtensionContract, new()
{

 public TContractType Create(
        IGenericRepository<TModelType> repo, 
        TContractType obj)        
    {                        I   
                // This is what is giving me trouble               
                var instance = (TContractType)
                    Activator.CreateInstance(typeof(TContractType), 
                    new object[] { obj }); 

                var modelMap = instance.ToModel(); 
                var ret = (dynamic)repo.Edit(modelMap);                    
                return ret.ToContract();
    } 

}

Code that actually works

Trying to make it generic as it is repetitive.

    [Microsoft.AspNetCore.Mvc.HttpPost]
    public patientContract.Patient CreatePatient(
        [Microsoft.AspNetCore.Mvc.FromBody] patientContract.Patient patient)
    {

            var map = patient.ToModel();
            var ret = _patientRepo.Add(map);
            return ret.ToContract();
    }

Extensions

public static class PatientContractExtension
{
    public static Model.Patient ToModel(this Contract.Patient patientContract)
    {  
        var map = Mapper.Map<Contract.Patient, Model.Patient>(patientContract);

        return map; 
    }

    public static Contract.Patient ToContract(this Model.Patient patientModel)
    {
        var map = Mapper.Map<Model.Patient, Contract.Patient>(patientModel);

        return map;
    }                    

}

Demodave
  • 6,242
  • 6
  • 43
  • 58
  • 2
    Why are you using `dynamic c` for the parameter instead of `T c`? – itsme86 Sep 20 '16 at 16:20
  • 2
    `ToModel` operates on `Client` so you need to declare `c` as `Client` or as a generic `TClient` with a constraint `where TClient : Client`. – Theodoros Chatzigiannakis Sep 20 '16 at 16:22
  • @Theodoros I don't know that TClient would be though so I can't specify it as a Client type – Demodave Sep 20 '16 at 16:25
  • 1
    This whole architecture makes me want to run away screaming. I've never seen anyone try to couple `Program` with another type's behavior in this manner. You're building a house of cards. Please revisit "encapsulation" and "single responsibility" principles. – itsme86 Sep 20 '16 at 16:27
  • 1
    Possible duplicate of [Extension method and dynamic object](http://stackoverflow.com/questions/5311465/extension-method-and-dynamic-object) – Matthew Whited Sep 20 '16 at 16:28
  • @itsme86, I'm not that was just a temp variable I'm trying to use reflection – Demodave Sep 20 '16 at 16:42
  • `dynamic` and reflection are not the same thing. You are going to have to explain what you are trying to do much more clearly. – Matthew Whited Sep 20 '16 at 16:44
  • Not trying to use dynamic at all. Removing from example. – Demodave Sep 20 '16 at 16:45
  • Extension methods are resolved by declared types, never by actual types. `patient.ToModel()` is just syntactic sugar for `PatientContractExtensions.ToModel(patient)`, which is found by the compiler only because it knows `patient` has been declared with type `Patient`. With a generic type `T`, this simply can't work. Rethink your approach to use something other than extension methods. Virtual methods of partial classes, dictionaries with delegates, visitors, and in the case of Automapper, the overload of `.Map` that accepts `Type`s. You can't use a static feature to solve a dynamic problem. – Jeroen Mostert Sep 21 '16 at 12:56

2 Answers2

3

Extension methods aren't supported by dynamic typing in a form of extension methods (called as if they were instance methods). Compiler search all public static classes with public static methods with a signature that contains keyword this and then it finds all classes that are extended by this Extension methods.

This doesn't work with dynamic as type is unknown on compile time. If you want to use extension method - use it with strongly typed members.

You can create base client class :

public abstract class BaseClient 
{ 
  public string FirstName { get; set; }
}

public class Client : BaseClient
{
}

And extend base type using generics and generic constraint in order to avoid upcasting to base type :

public static class ExtensionClass
{
    // we inform compiler that this method extends any type that is derived from base type
    public static TEntity ChangeName<TEntity>(this TEntity entity, string newName)
       where T : BaseClient            
    {
        entity.FirstName = newName;

        return entity;
    }

    public static TResult ToModel<TEntity, TResult>(this TEntity entity)
       where TEntity : BaseClient
       where TResult : BaseClient, new()
    {
        var result = new TResult();

        result.FirstName = entity.FirstName; 
        // ... rest of binding operations goes here

        return result;
    }
}

P.S. It is unclear why you need this ToModel() method as in code that you've provided there is only one model - Client1. You can invoke it like this :

var newModel = client.ToModel<SomeTypeDerivedFromBaseClient>();
Fabjan
  • 13,506
  • 4
  • 25
  • 52
  • Ok, so I'm gonna add to the complexity and that I do not know that TEntity would be that of a BaseClient as it could be of another type as well. I am trying to call the function within the base class. – Demodave Sep 20 '16 at 16:53
  • It was just an example, you can use any T1, T2 types – Fabjan Sep 21 '16 at 08:30
  • This did help me by allowing me to add the : BaseType – Demodave Sep 21 '16 at 13:26
0

I have fixed my problem by moving the Extension methods into my base class and making them regular methods.

Simple Code

public static T ToModel(T c)
{
    c.FirstName = "Extension Class";

    return c;
}

Call it as so

ToModel(c);

Actual Code

private TModelType ToModel(TContractType contract)
{
    var map = Mapper.Map<TContractType, TModelType>(contract);
    return map;
}

private TContractType ToContract(TModelType model)
{
    var map = Mapper.Map<TModelType, TContractType>(model);
    return map;
}

They then can be called like so

var modelMap = ToModel(obj);
Demodave
  • 6,242
  • 6
  • 43
  • 58