Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I somehow tidy up this (overuse?) of generics?

I'm building a generic flat file reader which looks something like this.

 public class GenericReader<TComposite, THeader, TData, TTrailer> 
    where TComposite : GenericComposite<THeader, TData, TTrailer>, new()
    where THeader : new()
    where TData : new()
    where TTrailer : new()
{
    public TComposite Read()
    {
        var composite = new TComposite();

        composite.Header = new THeader();
        composite.Data = new TData();
        composite.Trailer = new TTrailer();

        return composite;
    }        
}

It could be consumed like so.

var reader = new GenericReader<Composite<Header, Data, Trailer>, Header, Data, Trailer> ();

var composite = reader.Read();
Console.WriteLine(composite.Data.SomeProperty);

Console.ReadLine();

Here are the classes used.

public class Composite<THeader, TData, TTrailer> : GenericComposite<THeader, TData, TTrailer>
{

}

public class GenericComposite<THeader, TData, TTrailer>
{
    public THeader Header { get; set; }

    public TData Data { get; set; }

    public TTrailer Trailer { get; set; }
}

public class Header {
    public string SomeProperty { get { return "SomeProperty"; } } 
}

public class Data {
    public string SomeProperty { get { return "SomeProperty"; } } 
}

public class Trailer {
    public string SomeProperty { get { return "SomeProperty"; } } 
}

Is there a way how I could remove or encapsulate that generic type information in the GenericReader? I'm looking for an extra pair of eyes to show me something what I've been missing. We already did something with returning interfaces, and making the consumer do a cast, but that just moves the responsibility to the wrong location in my opinion, plus there is a small performance penalty.

Thanks.

Edit: I don't need the TComposite, I can just return the GenericComposite. How could I miss that?

public class GenericReader<THeader, TData, TTrailer> 
    where THeader : new()
    where TData : new()
    where TTrailer : new()
{
    public GenericComposite<THeader, TData, TTrailer> Read()
    {
        var composite = new GenericComposite<THeader, TData, TTrailer>();

        composite.Header = new THeader();
        composite.Data = new TData();
        composite.Trailer = new TTrailer();

        return composite;
    }        
}

public class GenericComposite<THeader, TData, TTrailer>
{
    public THeader Header { get; set; }

    public TData Data { get; set; }

    public TTrailer Trailer { get; set; }
}
like image 486
JefClaes Avatar asked Nov 27 '12 18:11

JefClaes


1 Answers

There's no way to remove the need for the type declarations on the generic constraints that you have.

However, your use case suggests that this is the most common behavior:

var reader = new GenericReader<Composite<Header, Data, Trailer>, 
    Header, Data, Trailer>();

If this is the case, where you can make assumptions about the frequency with which certain patterns are used, you can inherit a type (or set of types) from the generic classes with closed type definitions which can be used more easily.

In the above case, you can provide these classes for the base, most common cases (in addition to the generic definitions):

public class Composite : GenericComposite<Header, Data, Trailer> { }

public class GenericReader : GenericReader<
    Composite, Header, Data, Trailer>
{ }

Which would then be used like so:

var reader = new GenericReader();

var composite = reader.Read();

Console.WriteLine(composite.Data.SomeProperty);

Console.ReadLine();

You'll still have the types with the generic parameters to use for highly-specialized cases, but for common use cases (which you determine through analysis/domain knowledge), you can determine what the most common one is and provide classes with set type parameters to assist.

like image 100
casperOne Avatar answered Oct 31 '22 21:10

casperOne