IQueryable extension method is fine in normal method, but not within a generic method.
Compile error: IQueryable does not contain a definition for MyExtension and the best extension method overload DataExtensions.MyExtension(IQueryable Fred) requires a receiver of type IQueryable Fred
Goal is turn the // do something interesting below into a generic method that would work acrross all FlintstoneObject types.
public static class RepetitiveCodeBelow
{
public static int RepetitiveCode()
{
var count = 0;
using (var context = new DataContext())
{
foreach (var data in context.Freds.AsNoTracking().Where(item => item.PrimaryKey > 0).MyExtension())
{
// do something interesting
}
foreach (var data in context.Barnies.AsNoTracking().Where(item => item.PrimaryKey > 0).MyExtension())
{
// do something interesting
}
// more types omitted
}
return count;
}
}
Working version:
public List<Fred> GetFredList()
{
using (var context = new DataContext())
{
return context.Freds.AsNoTracking().MyExtension().ToList();
}
}
Wont compile:
public List<T> GetList<T>() where T : FlintstoneObject<T>, new()
{
using (var context = new DataContext())
{
return context.Set<T>().AsNoTracking().MyExtension().ToList();
}
}
Full sample
public abstract class FlintstoneObject<T> where T : class
{
public abstract int PrimaryKey { get; set; }
}
public class Fred : FlintstoneObject<Fred>
{
public override int PrimaryKey { get; set; }
}
public class Barny : FlintstoneObject<Barny>
{
public override int PrimaryKey { get; set; }
}
public static class DataExtensions
{
public static IQueryable<Fred> MyExtension(this IQueryable<Fred> queryable)
{
return queryable;
}
}
public class DataContext : DbContext
{
public DbSet<Fred> Freds { get; set; }
public DbSet<Barny> Barnies { get; set; }
public List<Fred> GetFredList()
{
using (var context = new DataContext())
{
return context.Freds.AsNoTracking().MyExtension().ToList();
}
}
public List<T> GetList<T>() where T : FlintstoneObject<T>, new()
{
using (var context = new DataContext())
{
return context.Set<T>().AsNoTracking().MyExtension().ToList();
}
}
}
Your code is probably lacking of this extension:
public static IQueryable<T> MyExtension<T>(this IQueryable<T> queryable)
{
return queryable;
}
to make it compilable.
Anyway, what you intended to do with your generic stuff looks strange:
public class Fred : FlintstoneObject<Fred>
I thought T should be some class other than Fred which is inheriting the generic type FlintstoneObject<T>.
Related
I have following code that does not compile
using System.Collections.Generic;
public interface IElement
{
}
public class AElement : IElement
{
public void DoSomethingSpecial()
{ }
}
public class Container<TElement>
{
public List<TElement> Elements { get; } = new();
}
public class Program
{
public static Container<IElement> GetContainer()
{
var concreteContainer = new Container<AElement>();
concreteContainer.Elements.ForEach(e => e.DoSomethingSpecial());
return concreteContainer; // Cannot implicitly convert type 'Container<AElement>' to 'Container<IElement>'
}
public static void Main()
{
var myContainer = GetContainer();
}
}
I read documentation about Covariance, Invariance, Contravariance and out Types.
And I am more confused than at the beginning.
Whats the way to fix this?
Code online: https://dotnetfiddle.net/85AgfT
You need to generate implicit conversion operator:
public class Container<IElement>
{
public List<IElement> Elements { get; } = new List<IElement>();
public static implicit operator Container<IElement>(Container<AElement> v)
{
//here you need to create Container<IElement> with your Container<AElement> 'v' values
return new Container<IElement>();
}
}
I finally got it working
using System.Collections.Generic;
public interface IContainer<out TElement>
{
}
public interface IElement
{
}
public class AElement : IElement
{
public void DoSomethingSpecial()
{ }
}
public class Container<TElement> : IContainer<TElement>
{
public List<TElement> Elements { get; } = new();
}
public class Program
{
public static IContainer<IElement> GetContainer()
{
var concreteContainer = new Container<AElement>();
concreteContainer.Elements.ForEach(e => e.DoSomethingSpecial());
return concreteContainer;
}
public static void Main()
{
var myContainer = GetContainer();
}
}
Make Container also an Interface and use an out Type parameter
I have below OSM material data structure that is different than entities and i have methods inside entities which i am forming compatible OSM material using AddToOsm method
public class FenestrationMaterial : Material
{ }
public class StandardOpaqueMaterial : OpaqueMaterial
{ }
public class OpaqueMaterial : Material
{ }
public class SurfaceConstruction : IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public Guid SurfaceTypeId { get; set; }
public IntendedSurfaceType SurfaceType { get; set; }
public List<Guid> LayerIds { get; set; }
public Construction AddToOsm(Model model, APIDbContext dbContext)
{
var construction = new Construction(model);
using var materials = new MaterialVector();
var fenestrationMaterialById = new Dictionary<Guid, FenestrationMaterial>();
var standardOpaqueMaterialById = new Dictionary<Guid, StandardOpaqueMaterial>();
var opaqueMaterialById = new Dictionary<Guid, OpaqueMaterial>();
foreach (var materialId in LayerIds.Where(i => i != default))
{
if (ProjectUtils.EntityById<OpaqueProjectMaterial>(dbContext, materialId) != default)
{
var OpaqueProjectMaterial = ProjectUtils.EntityById<OpaqueProjectMaterial>(dbContext, materialId);
materials.Add(
standardOpaqueMaterialById.GetOrCreate(OpaqueProjectMaterial.Id, () => OpaqueProjectMaterial.AddToOsm(model))
);
continue;
}
if (ProjectUtils.EntityById<AirGapMaterial>(dbContext, materialId) != default)
{
var airGapMaterial = ProjectUtils.EntityById<AirGapMaterial>(dbContext, materialId);
materials.Add(
opaqueMaterialById.GetOrCreate(airGapMaterial.Id, () => airGapMaterial.AddToOsm(model))
);
continue;
}
if (ProjectUtils.EntityById<GlazingMaterial>(dbContext, materialId) != default)
{
var glazingMaterial = ProjectUtils.EntityById<GlazingMaterial>(dbContext, materialId);
materials.Add(
fenestrationMaterialById.GetOrCreate(glazingMaterial.Id, () => glazingMaterial.AddToOsm(model))
);
continue;
}
}
construction.setLayers(materials);
return construction;
}
}
and then i do have entities for airGapmaterial, OpaqueProjectMaterial and GlazingMaterial
public class AirGapMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
......
......
public OpaqueMaterial AddToOsm(Model model)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
var airGapMaterial = new AirGap(model);
airGapMaterial.setName(this.Name);
.......
return airGapMaterial;
}
}
public class GlazingMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
.......
......
public FenestrationMaterial AddToOsm(Model model)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
var glazingMaterialComplexModel = new StandardGlazing(model);
glazingMaterialComplexModel.setName(this.Name);
........
return glazingMaterialComplexModel;
}
}
Is there any way I can use generic variable in-place of FenestrationMaterial and StandardOpaqueMaterial in the initialization of dictionaries (fenestrationMaterialById, standardOpaqueMaterialById) and extract the below common methods into single one?
I am looking kind of like this if possible
var fenestrationMaterialById = new Dictionary<Guid, T>();
That took a bit longer then I expected. Turns out the ProjectUtils bit really does throw a wrench into the works. In the following I trimmed things down to just the core concepts, added types missing from your post and did some refactoring to try and stream line things as much as possible. There is still plenty of room for improvement.
I also had to make a ton of assumptions about the underlying data and undocumented components. It looks to me like it would do what you're looking for but its certainly possible that I've made an assumption that is breaking on your end. I also tried to follow the pattern with the Util and DbContext usage, however, to me, those look pretty weak and should probably be reworked.
The core section you wanted to refactor is now:
public Construction AddToOsm(APIDbContext dbContext, Model model, IEnumerable<Guid> ids)
{
if(model is null) throw new ArgumentNullException(nameof(model));
var materialSources = ids.Where(i => i != default)
.Select(i => _projectUtils.EntityById<SourceData>(dbContext, i))
.Select(i => _projectUtils.ResolveToMaterialSource(i));
using var materialVector = new MaterialVector();
foreach (var materialSource in materialSources)
{
materialVector.Add(materialSource.AddToOsm(model));
}
return new Construction(model, materialVector);
}
And here's the rest, it should compile.
public class Model { }
public class Construction
{
public Construction(Model model, MaterialVector materials)
{
SetLayers(materials);
}
internal void SetLayers(MaterialVector materials)
{
}
}
public interface ISourceOfData
{
public string Name { get; set; }
}
public interface IIdentity<T>
{
[Key]
public Guid Id { get; set; }
}
public class IntendedSurfaceType { }
public class APIDbContext { }
public class KeyAttribute : Attribute { }
public class MaterialVector : IDisposable
{
public void Dispose()
{
}
internal void Add(IMaterial p)
{
}
}
public interface IMaterialTypeResolver
{
public IMaterialSource ResolveType(SourceData i);
}
public class MaterialTypeResolver : IMaterialTypeResolver
{
public IMaterialSource ResolveType(SourceData sourceData)
{
//Problem here - I don't know how your ProjectUtils.EntityById works internally
//So I'm guessing here that it uses a factory pattern
//this method needs to return an IMaterialSource...
//GlazingMaterialSource
//AirGapMaterialSource
//ect ect
//for a given set of database values
//essentially you need something coming from the database to tell the code
//which type to create, we pass that the data base values
//and it passes them later to the IMaterial in the AddToOsm call
//so the IMaterial implementation can do whatever custom work
return new OpaqueProjectMaterialSource(sourceData);
}
}
public class ProjectUtils
{
private IMaterialTypeResolver _MaterialTypeResolver;
public ProjectUtils(IMaterialTypeResolver materialTypeResolver)
{
_MaterialTypeResolver = materialTypeResolver;
}
public T EntityById<T>(APIDbContext dbContext, Guid materialId) where T : new()
//here you'd use the dbContext to populated this
//though this one at a time stuff is pretty inefficient
//it should be done as a single query if possible
=> new T();
public IMaterialSource ResolveToMaterialSource(SourceData i) => _MaterialTypeResolver.ResolveType(i);
}
public interface IMaterial
{
public string Name { get; set;}
}
public abstract class MaterialBase : IMaterial
{
public string Name {get;set;}
public MaterialBase(Model model, ISourceOfData source)
{
this.Name = source.Name;
}
}
public class AirGapMaterial : MaterialBase
{
public AirGapMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class StandardGlazingMaterial : MaterialBase
{
public StandardGlazingMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class FenestrationMaterial : MaterialBase
{
public FenestrationMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class StandardOpaqueMaterial : OpaqueMaterial
{
public StandardOpaqueMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class OpaqueMaterial : MaterialBase
{
public OpaqueMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class SurfaceConstruction : IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
private ProjectUtils _projectUtils;
public SurfaceConstruction(ProjectUtils projectUtils)
{
_projectUtils = projectUtils;
}
public Construction AddToOsm(APIDbContext dbContext, Model model, IEnumerable<Guid> ids)
{
if(model is null) throw new ArgumentNullException(nameof(model));
var materialSources = ids.Where(i => i != default)
.Select(i => _projectUtils.EntityById<SourceData>(dbContext, i))
.Select(i => _projectUtils.ResolveToMaterialSource(i));
using var materialVector = new MaterialVector();
foreach (var materialSource in materialSources)
{
materialVector.Add(materialSource.AddToOsm(model));
}
return new Construction(model, materialVector);
}
}
public interface IMaterialSource
{
public IMaterial AddToOsm(Model model);
}
public class SourceData : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
//add all the database loaded properties here
}
public abstract class MaterialSourceBase : IMaterialSource
{
protected SourceData SourceData {get;set;}
protected MaterialSourceBase(SourceData sourceData)
{
SourceData = sourceData;
}
public abstract IMaterial AddToOsm(Model model);
}
//So at this point you could generalize all of this down to a single
//factory pattern backed function as all we care about are
//given a set of database values, give me an IMaterial
public class OpaqueProjectMaterialSource : MaterialSourceBase
{
public OpaqueProjectMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new StandardOpaqueMaterial(model, base.SourceData);
}
public class AirGapMaterialSource : MaterialSourceBase
{
public AirGapMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new AirGapMaterial(model, base.SourceData);
}
public class GlazingMaterialSource : MaterialSourceBase
{
public GlazingMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new StandardGlazingMaterial(model, base.SourceData);
}
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?
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
I have an interface like this
public interface IPerson { }
And implementations
public class Fireman : IPerson
{
public string Name { get; set; }
public bool WithAssignedTruck { get; set; }
...
}
public class Pilot : IPerson
{
public string Name { get; set; }
public int Age { get; set; }
...
}
And pass them to a constructor
public class Registration : IRegistration
{
private readonly Fireman _fireman;
private readonly Pilot _pilot;
public Registration(Pilot pilot, Fireman fireman)
{
this._fireman = fireman;
this._pilot = pilot;
}
}
And here's what the initialization method looks like.
public T PopulateProfile<T>() where T : IPerson, new()
{
var personProfile = Activator.CreateInstance<T>();
...
return personProfile;
}
Please take note that this code is just an example.
I have a method that will set the value of each property of these classes which are from database. What I need to do is that, when I ask Ninject for any class that implements IPerson interface, Ninject should execute the method first, thus, Ninject will return an initialized class. Hope you could give me a hand. Thank you.
You can use Ninject.Extensions.Conventions in combination with an IBindingGenerator which generates a ToMethod binding:
BindingGenerator
internal class PersonBindingGenerator : IBindingGenerator
{
private static readonly MethodInfo PopulateOpenGenericMethodInfo =
typeof(IProfileService).GetMethod("PopulateProfile");
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(
Type type,
IBindingRoot bindingRoot)
{
yield return bindingRoot
.Bind(type)
.ToMethod(x => CreatePerson(x.Kernel.Get<IProfileService>(), type));
}
private static object CreatePerson(
IProfileService profileService,
Type type)
{
var closedGeneric = PopulateOpenGenericMethodInfo.MakeGenericMethod(type);
return closedGeneric.Invoke(profileService, new object[0]);
}
}
Bindings
kernel.Bind<IProfileService>().To<ProfileService>();
kernel.Bind(s => s
.FromThisAssembly()
.IncludingNonePublicTypes()
.SelectAllClasses()
.InheritedFrom<IPerson>()
.BindWith<PersonBindingGenerator>());
Test
Complete Test code for reference.
using FluentAssertions;
using Ninject;
using Ninject.Extensions.Conventions;
using Ninject.Extensions.Conventions.BindingGenerators;
using Ninject.Syntax;
using System;
using System.Collections.Generic;
using System.Reflection;
using Xunit;
namespace NinjectTest.SO36424126
{
public interface IPerson
{
string SomeValue { get; set; }
}
class BarPerson : IPerson
{
public string SomeValue { get; set; }
}
class FooPerson : IPerson
{
public string SomeValue { get; set; }
}
public interface IProfileService
{
T PopulateProfile<T>()
where T : IPerson, new();
}
internal class ProfileService : IProfileService
{
public T PopulateProfile<T>()
where T : IPerson, new()
{
var personProfile = Activator.CreateInstance<T>();
personProfile.SomeValue = "initialized";
return personProfile;
}
}
internal class PersonBindingGenerator : IBindingGenerator
{
private static readonly MethodInfo PopulateOpenGenericMethodInfo = typeof(IProfileService).GetMethod("PopulateProfile");
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
{
yield return bindingRoot
.Bind(type)
.ToMethod(x => CreatePerson(x.Kernel.Get<IProfileService>(), type));
}
private static object CreatePerson(IProfileService profileService, Type type)
{
var closedGeneric = PopulateOpenGenericMethodInfo.MakeGenericMethod(type);
return closedGeneric.Invoke(profileService, new object[0]);
}
}
public class Test
{
[Fact]
public void Foo()
{
var kernel = new StandardKernel();
kernel.Bind<IProfileService>().To<ProfileService>();
kernel.Bind(s => s
.FromThisAssembly()
.IncludingNonePublicTypes()
.SelectAllClasses()
.InheritedFrom<IPerson>()
.BindWith<PersonBindingGenerator>());
kernel.Get<BarPerson>().SomeValue.Should().Be("initialized");
}
}
}
public abstrct class Item
{
public string Name {get;set;}
}
public class Music : Item
{
public double Price {get;set;}
}
public class Game : Item
{
public string Image {get;set;}
}
public class Inventory
{
private IList<Item> _games;
private IList<Item> _musics;
public Inventory()
{
_games = new List<Item>();
_musics = new List<Item>();
}
public void Add<T>(T item) where T : Item
{
if(typeof(T) == typeof(Game))
{
_game.add(item);
}
if(typeof(T) == typeof(Music))
{
_muisc.add(item);
}
public List<T> GetCollection<T>() where T : Item
{
return (List<T>) _muiscs;
}
class Porgram
{
static void Main(string[] args)
{
Inventory inventory = new Inventory();
var music1 = new Music(){ Name ="aa", Price = 10};
var Music2 = new Music() { Name ="bb", price = 20 };
inventory.add(music1);
inventory.add(music2);
List<Music> myMusics = inventory.GetCollection<Music>();
}
The code will compile but it will throw exception when try to Call Get Collection method.
I am not sure really why? I am guess i am using generic incorrect.
A List<Item> cannot be cast to a List<Music>. While Music is a subclass of Item, generic types do not follow the same inheritance pattern as their collection type. The simplest way to fix your code would be to replace the cast in your GetCollection method with a call to the Linq extension method cast, followed by ToList. That said, I think your entire class could be redesigned to handle this sort of inheritence better.
So, your GetCollection method looks like this:
public List<T> GetCollection<T>() where T : Item
{
return _musics.Cast<T>().ToList();
}
Try this code:
public abstract class Item
{
public string Name { get; set; }
}
public class Music : Item
{
public double Price { get; set; }
}
public class Game : Item
{
public string Image { get; set; }
}
public class Inventory<E> where E : Item
{
private IList<E> _games;
private IList<E> _musics;
public Inventory()
{
_games = new List<E>();
_musics = new List<E>();
}
public void Add(E item)
{
if (typeof(E) == typeof(Game))
{
_games.Add(item);
}
if (typeof(E) == typeof(Music))
{
_musics.Add(item);
}
}
public List<E> GetCollection()
{
return _musics;
}
}
public class Program
{
public static void Main(string[] args)
{
Inventory<Item> inventory = new Inventory<Item>();
var music1 = new Music() { Name = "aa", Price = 10 };
var music2 = new Music() { Name = "bb", Price = 20 };
inventory.Add(music1);
inventory.Add(music2);
List<Item> myMusics = inventory.GetCollection();
}
}
You need to declare your Inventory class to be generic where it takes in a class that extend Item
Also: It looks like you wrote the code, and didn't copy and paste it... I don't know why you did that...
Just modify your GetCollection method as
public List <T> GetCollection<T>() where T :Item
{
if (typeof(T) == typeof(Game))
{
return _games.Cast<T>().ToList();
}
if (typeof(T) == typeof(Music))
{
return _musics.Cast<T>().ToList(); ;
}
return null;
}