Avatar billede johny Nybegynder
11. september 2008 - 14:23 Der er 2 kommentarer og
1 løsning

Generic/Extension Methods problemer.

Jeg er pt. forholdsvis grøn inden for brugen af typestærke klasser og Extension Methods, men forstår dog at bruge dem. Problemet er så imidlertid nu hvor jeg skal blande de to begreber sammen, så kan jeg ikke længere finde ud af hvordan det skal løses rent syntaksmæssigt, hvis det overhovedet kan lade sig gøre?

Jeg har lige nu nogle klasser som har et Data Transfer Object ækvivalent, og det har jeg struktureret vha. 2 interfaces som ser sådan her ud:

public interface IDto {} //denne bliver implementeret af alle DTO klasser.

public interface IConvertableToDto<TDto> where TDto : IDto {
  TDto ToDto();
} //denne bliver implementeret af alle de klasser der kan konverteres til DTO.

Vha. de 2 ovenstående interfaces, kan jeg så smide en metode i mine konverterbare klasser der håndterer konverteringen, og så bare kalde den når jeg skal bruge et DTO.

Jeg vil så bare gerne have systematiseret det yderligere, ved også at kunne kalde en enkelt metode når der er tale om en liste af konverterbare objekter i stedet. Dette vil jeg så løse vha. ExtensionMethods på List<IEnumerable>.

Dette har jeg gjort før ved at lave en metode der så således ud:

public static List<TDto> ToDtoList<TConvertable,TDto>(this List<TConvertable> convertableList) where TConvertable : IConvertableToDto, where TDto : IDto {
  //Implementation her...
}

Den her gang vil jeg så gerne undgå at skulle udfylde <TConvertable,TDto>, da klasserne og metoderne godt selv ved hvilke klasser der skal kontaktes. Så min umiddelbare løsning så sådan her ud:

public static List<T> ConvertToDtoList(this List<IHasConvertToDto<T>> convertableList) {
  //Implementation her...
}

Men ja, det vil jo så naturligvis ikke compile, men det illustrerer meget godt det jeg forsøger at opnå.

Så spørgsmålet er, kan det lade sig gøre, og hvis ja, hvordan?

På forhånd tak.. :)
Avatar billede arne_v Ekspert
20. september 2008 - 23:20 #1
Interessant problem-stilling.

Jeg antager at du har en kode som ligner denne:

using System;
using System.Collections.Generic;

namespace E
{
    public interface IDto
    {
        string WhatDto();
    }
    public interface IConvertableToDto<TDto> where TDto : IDto
    {
          TDto ToDto();
    }
    public class XDto : IDto
    {
        public string WhatDto()
        {
            return "I am a X DTO";
        }
    }
    public class YDto : IDto
    {
        public string WhatDto()
        {
            return "I am a Y DTO";
        }
    }
    public class X : IConvertableToDto<XDto>
    {
        public XDto ToDto()
        {
            return new XDto();
        }
    }
    public class Y : IConvertableToDto<YDto>
    {
        public YDto ToDto()
        {
            return new YDto();
        }
    }
    public static class MyExtensions
    {
        public static List<TDto> ToDtoList<TConvertable,TDto>(this List<TConvertable> convertableList) where TConvertable : IConvertableToDto<TDto> where TDto : IDto
        {
            List<TDto> result = new List<TDto>();
            foreach(TConvertable o in convertableList)
            {
                result.Add(o.ToDto());
            }
            return result;
        }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            X ox = new X();
            Console.WriteLine(ox.ToDto().WhatDto());
            Y oy = new Y();
            Console.WriteLine(oy.ToDto().WhatDto());
            List<X> lstx = new List<X>();
            lstx.Add(new X());
            lstx.Add(new X());
            List<XDto> lstxdto = lstx.ToDtoList<X,XDto>();
            foreach(XDto o in lstxdto)
            {
                Console.WriteLine(o.WhatDto());
            }
            List<Y> lsty = new List<Y>();
            lsty.Add(new Y());
            lsty.Add(new Y());
            List<YDto> lstydto = lsty.ToDtoList<Y,YDto>();
            foreach(YDto o in lstydto)
            {
                Console.WriteLine(o.WhatDto());
            }
            Console.ReadKey();
        }
    }
}

Og i foerste omgang er den meget vanskelig at goere paenere.

Hvis metoden ikke er generisk kan man ikke bruge generiske argumenter.

Og hvis man undlader generiske argumenter ved at bruge interfaces, saa matcher
argumenterne ikke.

Bedste workaround er efter min mening at skrue lidt ned for det generiske og lidt op
for det objekt orienterede.

Eksempel:

using System;
using System.Collections.Generic;

namespace E
{
    public interface IDto
    {
        string WhatDto();
    }
    public interface IConvertableToDto
    {
          IDto ToDto();
    }
    public class XDto : IDto
    {
        public string WhatDto()
        {
            return "I am a X DTO";
        }
    }
    public class YDto : IDto
    {
        public string WhatDto()
        {
            return "I am a Y DTO";
        }
    }
    public class X : IConvertableToDto
    {
        public IDto ToDto()
        {
            return new XDto();
        }
    }
    public class Y : IConvertableToDto
    {
        public IDto ToDto()
        {
            return new YDto();
        }
    }
    public static class MyExtensions
    {
        public static List<IDto> ToDtoList<TConvertable>(this List<TConvertable> convertableList) where TConvertable : IConvertableToDto
        {
            List<IDto> result = new List<IDto>();
            foreach(TConvertable o in convertableList)
            {
                result.Add(o.ToDto());
            }
            return result;
        }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            X ox = new X();
            Console.WriteLine(ox.ToDto().WhatDto());
            Y oy = new Y();
            Console.WriteLine(oy.ToDto().WhatDto());
            List<X> lstx = new List<X>();
            lstx.Add(new X());
            lstx.Add(new X());
            List<IDto> lstxdto = lstx.ToDtoList();
            foreach(IDto o in lstxdto)
            {
                Console.WriteLine(o.WhatDto());
            }
            List<Y> lsty = new List<Y>();
            lsty.Add(new Y());
            lsty.Add(new Y());
            List<IDto> lstydto = lsty.ToDtoList();
            foreach(IDto o in lstydto)
            {
                Console.WriteLine(o.WhatDto());
            }
            Console.ReadKey();
        }
    }
}
Avatar billede johny Nybegynder
22. september 2008 - 10:44 #2
Så det kan altså umiddelbart ikke lade sig gøre at blande de to ting direkte? For det vil jo helt klart være det mest optimale. Det jeg netop helst vil undgå, er at typecaste, og med den løsning du her præsenterer, har jeg jo kun en liste af IDto objekter, hvilket ikke stemmer med den returntype jeg har for den metode hvor jeg har brug for at kalde ToDto(). Der skal jeg returnere en liste af YDto'er.

Jeg har jo netop givet de forskellige klasser al den information der skal til, for at det hele kan køre typestærkt, men i det øjeblik jeg får Extesion Methods blandet ind over, er der lige pludselig en del den ikke ved. Det generer mig egentlig en hel del, da det jo burde være så lige til.

Jeg har selv brugt begge løsninger du her foreslår, men ja, det jeg jo egentlig ville er nok ikke muligt i .NET sådan som det ser ud lige nu så. Så smid et svar, så kommer der et din vej, og så må jeg til at klage til Microsoft efter et hotfix... ;)
Avatar billede arne_v Ekspert
22. september 2008 - 11:56 #3
Jeg kunne ihvertfald ikke finde bedre maader i C#.

Og et svar.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester