Tech Off Thread

16 posts

Can I overload a Ctor on a Generic Type?

Back to Forum: Tech Off
  • User profile image
    phreaks


    public class LogEngine<T> where T : BaseLogEngine, new()
    {
        BaseLogEngine _logger;
    
        public LogEngine()
        {
            _logger = new T();
        }
        // Is there a way to do this?
        public LogEngine(String LogFilePath)
        {
            _logger = new T(LogFilePath);
        }
        public void Log(LogItem logItem)
        {    
            _logger.Log(logItem);
        }
    }
    

  • User profile image
    evildictait​or

    Not unless you own the base-type (and rightly so - constructors make sure the object is consistent, and if you don't own the base-type, you won't know how to instantiate it's private variables so it's consistent).


    class MyGenericType<T> {
      private T myvar;
      internal void initiantiate(T val){
        myvar = val;
      }

      // allow inheriting types to avoid the constructor MyGenericType(T val)
      protected MyGenericType() {}
      public MyGenericType(T val){
        myvar = val;
      }
    }


    class MyInheritingType : MyGenericType<int> {
      // this constructor uses the public constructor
      public MyInheritingType(int init) : base(init) { }

      // this uses the protected constructor of the base object.
      public MyInheritingType() : base()  {
        base.instantiate(-1);
      }
    }



    class MyProgram {
      static void Main(){
        var a = new MyGenericType<int> ( 100 ) // valid
        var b = new MyGenericType<int> ()  // invalid

        var c = new MyInheritingType() // valid.
      }
    }

  • User profile image
    Sven Groot

    Evil, I think you misunderstood his question. It has nothing to do with inheritance.

    Preaks: no, you cannot. While you can use the new() constraint to specify that the type must have a parameterless constructor, you cannot use it to require any other constructors, so you cannot instantiate T like that.

    The only way to do that would be to use Activator.CreateInstance (which is what "new T()" compiles into anyway), but of course you won't get a compile-time check to verify the correct constructor exists.

  • User profile image
    phreaks

    Thanks, but I want to allow both the default and overloaded Ctors.


    Ahh, Sven. Thanks. I was just now beginning to wander down the Activator.CreateInstance path, but basically you're saying that I lose the benefits of generics If I do, right?

  • User profile image
    joechung

    Here's a clumsy attempt at working around C#'s gimpy constructors. ConsoleLogEngine.Create is a factory method that wraps a parameterized constructor, i.e., return new ConsoleLogEngine(string).

    /// <summary> /// Example: /// var engine = new LogEngine<ConsoleLogEngine>(ConsoleLogEngine.Create, @"C:\foo.log"); /// engine.Log(new LogItem() { DateTime = DateTime.Now, Message = "Hello, World!" }); /// </summary> public class LogEngine<T>
     where T : BaseLogEngine, new() { BaseLogEngine logger; public LogEngine() { this.logger = new T(); } public LogEngine(Func<string, T> ctor, string logPath) { this.logger = ctor(logPath); } public void Log(LogItem item) { this.logger.Log(item); } }

    P.S.  How do you do that nice syntax coloring and highlighting in Channel 9 posts? (Edit: Thanks, Sven!)

  • User profile image
    Sven Groot

    [ code language="C#" ] code here [ /code ]

    But without the spaces.

    Personally, I would try to find a solution where it is not necessary to use multiple constructors. Change the design of BaseLogEngine and its derived classes so that that's no longer necessary. Just use a property or something to specify the log file.

  • User profile image
    TommyCarlier

    joechung said:

    Here's a clumsy attempt at working around C#'s gimpy constructors. ConsoleLogEngine.Create is a factory method that wraps a parameterized constructor, i.e., return new ConsoleLogEngine(string).

    /// <summary> /// Example: /// var engine = new LogEngine<ConsoleLogEngine>(ConsoleLogEngine.Create, @"C:\foo.log"); /// engine.Log(new LogItem() { DateTime = DateTime.Now, Message = "Hello, World!" }); /// </summary> public class LogEngine<T>
     where T : BaseLogEngine, new() { BaseLogEngine logger; public LogEngine() { this.logger = new T(); } public LogEngine(Func<string, T> ctor, string logPath) { this.logger = ctor(logPath); } public void Log(LogItem item) { this.logger.Log(item); } }

    P.S.  How do you do that nice syntax coloring and highlighting in Channel 9 posts? (Edit: Thanks, Sven!)

    Why make it difficult? If you provide a constructor that can take an instance of the base type, it doesn't matter how that instance is created:

    public class LogEngine<TLogger> where TLogger : BaseLogEngine, new()
    {
        readonly TLogger _logger;
        public LogEngine()
        {
            _logger = new TLogger();
        }
    
        public LogEngine(TLogger logger)
        {
            if (logger == null) throw new ArgumentNullException("logger");
            _logger = logger;
        }
    
        public void Log(LogItem item)
        {
            _logger.Log(item);
        }
    }

  • User profile image
    joechung

    I like your approach!  The only thing my approach really bought -- and I didn't even do it so it's questionable -- was the ability to instantiate the logger lazily.

    I harped on this in the past and got yelled at by W3bb0 and stev0, but I wish C# would allow you to alias constructors with delegates, i.e., new LogEngine() is Constructor<LogEngine> == true.  Constructors in the CLR are weird.

  • User profile image
    Sven Groot

    TommyCarlier said:
    joechung said:
    *snip*

    Why make it difficult? If you provide a constructor that can take an instance of the base type, it doesn't matter how that instance is created:

    public class LogEngine<TLogger> where TLogger : BaseLogEngine, new()
    {
        readonly TLogger _logger;
        public LogEngine()
        {
            _logger = new TLogger();
        }
    
        public LogEngine(TLogger logger)
        {
            if (logger == null) throw new ArgumentNullException("logger");
            _logger = logger;
        }
    
        public void Log(LogItem item)
        {
            _logger.Log(item);
        }
    }

    I agree with Tommy, that's actually the best solution.

  • User profile image
    stevo_

    joechung said:

    I like your approach!  The only thing my approach really bought -- and I didn't even do it so it's questionable -- was the ability to instantiate the logger lazily.

    I harped on this in the past and got yelled at by W3bb0 and stev0, but I wish C# would allow you to alias constructors with delegates, i.e., new LogEngine() is Constructor<LogEngine> == true.  Constructors in the CLR are weird.

    Me? hmm- also, new LogEngine() is just Func<LogEngine>, you should try to make as few dependencies as possible.. for example, the Func<LogEngine> just expects something that returns a LogEngine, its more flexible as you aren't trying to force it to be a constructor- it could be anything (such as a service locator etc).

  • User profile image
    phreaks

    stevo_ said:
    joechung said:
    *snip*
    Me? hmm- also, new LogEngine() is just Func<LogEngine>, you should try to make as few dependencies as possible.. for example, the Func<LogEngine> just expects something that returns a LogEngine, its more flexible as you aren't trying to force it to be a constructor- it could be anything (such as a service locator etc).
    Wow, thanks for all the great replies.

    That's pretty cool Tommy but how does that buy me a ctor that let's me over-ride the filepath string?

    I think that I have bigger design issues...

    It doesn't seem right to have  a FielPath field, because some of the concrete classes won't implment them (such as EmailLogger, DataBaseLogger).

    But I want this to be flexible so that I can chain all the loggers together if I want to log to many different types (DB, Email, Xml, etc)

    Any good ideas?

  • User profile image
    Curt Nichols

    phreaks said:
    stevo_ said:
    *snip*
    Wow, thanks for all the great replies.

    That's pretty cool Tommy but how does that buy me a ctor that let's me over-ride the filepath string?

    I think that I have bigger design issues...

    It doesn't seem right to have  a FielPath field, because some of the concrete classes won't implment them (such as EmailLogger, DataBaseLogger).

    But I want this to be flexible so that I can chain all the loggers together if I want to log to many different types (DB, Email, Xml, etc)

    Any good ideas?
     

    Phreaks, it might be better to decompose this into two types, LogEngine and LogWriter, where the first is a concrete type that implements the publicly visible operations of writing a log entry and the second is  an interface  or abstract type, from which you can specialize types for file, email, database, etc. If needed, create a LogEngineFactory that creates a concrete LogWriter and hands it to a new LogEngine instance according to configuration.

     

    I don't see the value of having this be generic, you can lose that part without losing any value or functionality.

  • User profile image
    Sven Groot

    Actually, from what you're trying to do, I'd suggest you just use log4net. Tongue Out

  • User profile image
    Cannot​Resolve​Symbol

    phreaks said:
    stevo_ said:
    *snip*
    Wow, thanks for all the great replies.

    That's pretty cool Tommy but how does that buy me a ctor that let's me over-ride the filepath string?

    I think that I have bigger design issues...

    It doesn't seem right to have  a FielPath field, because some of the concrete classes won't implment them (such as EmailLogger, DataBaseLogger).

    But I want this to be flexible so that I can chain all the loggers together if I want to log to many different types (DB, Email, Xml, etc)

    Any good ideas?
    With Tommy's approach, you can do something like LogEngine<FileLogWriter> = new LogEngine(new FileLogWriter("C:\log.txt"));  (pardon my syntax if it's not correct; I've been doing too much Objective-C lately and might have gotten the syntax for generics mixed up...  Obj-C doesn't have generics).

    There isn't really any value to generics here, though.  You could do the exact same thing by having all your loggers inherent from a common interface (e.g. ILogWriter if you're into the whole .NET style guide thing).  If you want to be able to "chain" loggers and log to multiple sources, have your log engine contain a list of ILogWriter instances, and add instances with something like LogEngine.registerLogWriter(logger);.  You could then iterate through the list of loggers whenever you need to log something.

    Of course, you could just use Log4Net, and then all of this is solved for you--  no additional design necessary Tongue Out

  • User profile image
    phreaks

    Sven Groot said:
    Actually, from what you're trying to do, I'd suggest you just use log4net. Tongue Out
    Yeah, I was already thinking about implementing log4net in the text and email loggers, but the DatabaseLogger is the real concern as it will be the one mostly used. The other loggers are just tthere for dev/debugging purposes and to provide a richer api.

    I've never used Log4Net for Db logging, is it nice?

  • User profile image
    Sven Groot

    phreaks said:
    Sven Groot said:
    *snip*
    Yeah, I was already thinking about implementing log4net in the text and email loggers, but the DatabaseLogger is the real concern as it will be the one mostly used. The other loggers are just tthere for dev/debugging purposes and to provide a richer api.

    I've never used Log4Net for Db logging, is it nice?
    Sorry, I haven't done that either. I know it can do it, but I haven't used it for that.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.