Resolved Nested Classes

RobertbNZ

Active member
Joined
Jun 26, 2020
Messages
37
Programming Experience
Beginner
I need to map COBOL record layouts into C# (or VB) classes. COBOL record layouts may have many definition levels, and I need to be able to cope with the rule that a field name may be repeated, provided that there is a way of distinguishing which field you mean with full qualification. I thought that I could do the same in C# when I found that a class can be nested within another, so I could directly map the COBOL structure with C# classes corresponding to input and output web service messages like this: -
C#:
C#:
namespace MyJSv.Models
{
    public class IJSPG2
    {
        public string JZ_Function { get; set; }
        public int JZ_Employee_Skip { get; set; }
        public class JZEmployee
        {
            public string EMPNO { get; set; }
...
            public string JZ_CURRENCY { get; set; }
        }
        public class ViewState
        {
            public string CheckSum_Employee { get; set; }
        }
    }
}
However I found (and this page confirmed) that I can't refer to IJSPG2.JZEMPLOYEE.EMPNO. To solve the problem I either have to flatten the message layout and deal with name ambiguity by renaming fields, or use separate classes. I'll handle repeating groups (arrays), by keeping them as separate classes and using C# logic to put the data into an in-memory database (as in this tutorial).

Before I go down this path can somebody please confirm for me that there is no way of creating nested classes (or structures, or any other object type) that I can refer to in this way. I'd prefer not to expend a lot of effort and then find out I didn't need to :)
 
Perhaps, I'm not seeing the problem. After taking crash course in COBOL layouts, the following works just fine for me:
C#:
namespace ConsoleTest
{
    class MailingRecord
    {
        public class Contact_
        {
            public class President_
            {
                public string LastName { get; set; }
                public string FirstName { get; set; }
            }

            public class VPMarketing_
            {
                public string LastName { get; set; }
                public string FirstName { get; set; }
            }

            public class AlternateContact_
            {
                public string Title { get; set; }
                public string LastName { get; set; }
                public string FirstName { get; set; }
            }

            public President_ President { get; }  = new President_();
            public VPMarketing_ VPMarketing { get; } = new VPMarketing_();
            public AlternateContact_ AlternateContact { get; }  = new AlternateContact_();
        }

        public string CompanyName { get; set; }
        public Contact_ Contact { get; } = new Contact_();
        public string Address { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
    }

    class Program
    {
        static void Main()
        {
            var mailingRecord = new MailingRecord();

            mailingRecord.CompanyName = "Enron";
            mailingRecord.Contact.President.LastName = "Skilling";
            mailingRecord.Contact.President.FirstName = "Jeffrey";
            mailingRecord.Contact.AlternateContact.Title = "CEO";
            mailingRecord.Contact.AlternateContact.LastName = "Lay";
            mailingRecord.Contact.AlternateContact.FirstName = "Kenneth";
        }
    }
}

I suspect what you were stumbling against was that the following is not allowed in C#
C#:
class Foo
{
    class Bar
    {
    }

    Bar barInstance;    // !!! Error: CS0102: The type 'Foo' already contains a definition for 'Bar'
}

So notice how in my code, I decided to name the type with an "_" suffix, and then declared the property exposing an instance of that type without the underscore.
 
Thank you for helping Skydiver. I've been working with VB.NET and ASP.NET for > 15 years, but I'm a complete novice with C#: I've just started with it because I need to write a WebAPI in .NET Core, and this requires C#. So please make allowances if I ask dumb questions. Also my reply is me thinking out loud as I work out what's going on.

I have developed a solution and project MyJSv following the style of the the Web API Tutorial, so the definition of IJSPG2 is in folder Models. Now I'm using program.cs to experiment with this problem. Initially program.cs is
C#:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace MyJSv
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
...
    }
}

With IJSPG2 defined as in my first post, I added (or tried) these statements just before CreateHostBuilder(args)...
C#:
            var ijspg2 = new Models.IJSPG2();
            ijspg2.JZ_Function = "E";
            ijspg2.JZEmployee.EMPNO = 90;   //  I couldn't write this
The only "fields" (= C# classes) that I can assign to are JZ_Function and JS_Employee_Skip. To assign to EMPNO I had to create another definition: -
C#:
            var ijspg2 = new Models.IJSPG2();
            ijspg2.JZ_Function = "E";
            var jzemployee = new Models.IJSPG2.JZEmployee();
            jzemployee.EMPNO = "90";

Your trick of suffixing the class names looks interesting. I suffixed JZEmployee and Viewstate, changing the definition to
C#:
namespace MyJSv.Models
{
    public class IJSPG2
    {
        public string JZ_Function { get; set; }
        public int JZ_Employee_Skip { get; set; }
        public class JZEmployee_
        {
            public string EMPNO { get; set; }
...         public string JZ_CURRENCY { get; set; }
        }
        public class ViewState_
        {
            public string CheckSum_Employee { get; set; }
        }
    }
}
But underscores alone didn't help: I still couldn't refer to ijspg2.JZEmployee.EMPNO without a separate class definition for jzemployee. BUT THEN I realized that you HAD shown me the answer. You didn't just suffix the name, you added properties within class MailingRecord to expose these lower-level objects. I added
C#:
public JZEmployee_ JZEmployee { get; set; }
to the definition of IJSPG2 at the same logical level as the class JZEmployee_. Now I was able to write
C#:
            var ijspg2 = new Models.IJSPG2();
            ijspg2.JZ_Function = "E";
            ijspg2.JZEmployee.EMPNO = "90";
So this gives me a model to support a COBOL hierarchical record layout. I think that it will be simpler to generate a C# object like IJSPG2 with these supporting properties than to change the way in which MANASYS Jazz generates the COBOL. It will be a while before I can get to compile and run test the full solution, but I'm happy to set this as Answered now. Thank you Skydiver.
 
Notice that I deliberately did the following:
C#:
public President_ President { get; }  = new President_();
That is almost equivalent to:
C#:
public readonly President_ President = new President_();

The reason why I instantiate the President_ class right away into the President *property* that only has a getter is so that I I won't forget to set it, AND more importantly, after it is set, nobody swaps out the object from under me.

With the the readonly President *field* in the equivalent code, I get the same effect where once the instance is created and set, nobody can swap the object out from under me. But it doesn't follow one of the C# best practices of not exposing public fields.
 
Thank you Skydiver, I'll remember this as I continue. MANASYS Jazz generates web service programs like JSPG2, with input and output messages IJSPG2 and OJSPG2, which become operations in the CICS web service MyJSv. My goal is to provide a web API that encapsulates almost all the rules of the service, as shown in the diagram below. Consuming the web service will then be simplified, and rules like "Viewstate must be returned unchanged with Update" implemented. Client-side validation will detect many errors instantly, rather than relying on server-side validation to return a message, thus providing a better UI. Each program will have its own API Controller, which is the "Interface" in the diagram, and its input and output messages will be class hierarchies like IJSPG2 and OJSPG2 in Models. It seems to me that the obvious thing is to put these validation rules into the Set objects of the IJSPG2 classes ("fields"). The output (OJSPG2) classes will presumably be read-only.

I have a lot to learn as I develop this feature, and I will be back with many questions. I really value guidance on best practice, so thank you for your help so far.
.
WebAPI.jpg
 
Back
Top Bottom