Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual Member in Constructor, workaround

Tags:

c#

I have a class, BaseEmailTemplate, that formats an email, and I want to create a derived type that can overrule the defaults. Originally my base constructor -

public BaseEmailTemplate(Topic topic)
        {

                CreateAddresses(topic);
                CreateSubject(topic);
                CreateBody(topic);

        }

... (Body/Addresses)

protected virtual void CreateSubject(Topic topic)
    {
        Subject = string.Format("Base boring format: {0}", topic.Name);
    }

And in my derived

public NewEmailTemplate(Topic topic) : Base (topic)
        {

            //Do other things
        }

protected override void CreateSubject(Topic topic)
        {
            Subject = string.Format("New Topic: {0} - {1})", topic.Id, topic.Name);
        }

Of course this leads to the error discussed here: Virtual member call in a constructor

So to be absolutely blunt about this - I don't want to have to call the same methods in every derived type. On the flip side, I need to be able to change any/all. I know another base has a different subset of addresses, but the body and subject will be the default.

All three methods must be called, and the ability to alter any one of them needs to be available on a per derived basis.

I mean the thing everyone seems to be saying is an unintended consequence of using virtual seems to be my exact intention.. Or maybe I'm in too deep and singly focused?

UPDATE- Clarification

I understand why virtual members in the constructor is bad, I appreciate the answers on that topic, though my question isn't "Why is this bad?" its "Ok this is bad, but I can't see what better serves my need, so what do I do?"

This is how it is currently implemented

 private void SendNewTopic(TopicDTO topicDto)
        {
            Topic topic = Mapper.Map<TopicDTO , Topic>(topicDto);
            var newEmail = new NewEmailTemplate(topic);
            SendEmail(newEmail);  //Preexisting Template Reader infrastructure

            //Logging.....
        }

I'm dealing with a child and grandchild. Where I came in there was only newemailtemplate, but I have 4 other tempaltes I now have to build, but 90% of the code is reusable. Thats why I opted to create BaseEmailTemplate(Topic topic). BaseTemplate creates things like Subject and List and other things that SendEmail is expecting to read.

  NewEmailTemplate(Topic topic): BaseEmailTemplate(Topic topic): BaseTemplate, IEmailTempate

I would prefer not have to require anyone who follows my work have to know that

 var newEmail = new NewEmailTemplate();
 newEmail.Init(topic);

is required every single time it is used. The object would be unusable without it. I thought there were many warnings about that?

like image 564
Seth Avatar asked Mar 20 '26 20:03

Seth


2 Answers

[10.11] of the C# Specification tells us that object constructors run in order from the base class first, to the most inherited class last. Whereas [10.6.3] of the specification tells us that it is the most derived implementation of a virtual member which is executed at run-time.

What this means is that you may receive a Null Reference Exception when attempting to run a derived method from the base object constructor if it accesses items that are initialized by the derived class, as the derived object has not had it's constructor run yet.

Effectively, the Base method's constructor runs [10.11] and tries to reference the derived method CreateSubject() before the constructor is finished and the derived constructor can be run, making the method questionable.

As has been mentioned, in this case, the derived method seems to only rely upon items passed as parameters, and may well run without issue.

Note that this is a warning, and is not an error per se, but an indication that an error could occur at runtime.

This would not be a problem if the methods were called from any other context except for the base class constructor.

like image 107
Claies Avatar answered Mar 22 '26 08:03

Claies


A factory method and an initialize function is an effective workaround for this situation.

In the base class:

private EmailTemplate()
{
   // private constructor to force the factory method to create the object
}

public static EmailTemplate CreateBaseTemplate(Topic topic)
{
    return (new BaseEmailTemplate()).Initialize(topic);
}

protected EmailTemplate Initialize(Topic topic)
{
   // ...call virtual functions here
   return this;
}

And in the derived class:

public static EmailTemplate CreateDerivedTemplate(Topic topic)
{
    // You do have to copy/paste this initialize logic here, I'm afraid.
    return (new DerivedEmailTemplate()).Initialize(topic);
}

protected override CreateSubject...

The only exposed method to create an object will be through the factory method, so you don't have to worry about the end user forgetting to call an initialize. It's not quite as straight-forward to extend, when you want to create further derived classes, but the objects themselves should be quite usable.

like image 42
Tanzelax Avatar answered Mar 22 '26 09:03

Tanzelax



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!