Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I properly work with calling methods on related but different classes in C#

To be honest I wasn't sure how to word this question so forgive me if the actual question isn't what you were expecting based on the title. C# is the first statically typed language I've ever programmed in and that aspect of it has been an absolute headache for me so far. I'm fairly sure I just don't have a good handle on the core ideas surrounding how to design a system in a statically typed manner.

Here's a rough idea of what I'm trying to do. Suppose I have a hierarchy of classes like so:

abstract class DataMold<T>
{
    public abstract T Result { get; }
}

class TextMold : DataMold<string>
{
  public string Result => "ABC";
}  

class NumberMold : DataMold<int>
{
   public int Result => 123
}

Now suppose I want to make a list of item where the items can be any kind of mold and I can get the Result property of each item in a foreach loop like so:

List<DataMold<T>> molds = new List<DataMold<T>>();
molds.Add(new TextMold());
molds.Add(new NumberMold());

foreach (DataMold<T> mold in molds)
    Console.WriteLine(mold.Result);

As you probably already know, that doesn't work. From what I've read in my searches, it has to do with the fact that I can't declare the List to be of type DataMold<T>. What is the correct way to go about something like this?

like image 908
Aaron Beaudoin Avatar asked Jun 22 '18 22:06

Aaron Beaudoin


People also ask

Can you call a function from a different class?

In Java, a method can be invoked from another class based on its access modifier. For example, a method created with a public modifier can be called from inside as well as outside of a class/package. The protected method can be invoked from another class using inheritance.

How do you call a function from one class from another class in C++?

Class B inherits from Class A, and I want class A to be able to call a function created in class B. using namespace std; class B; class A { public: void CallFunction () { B b; b. bFunction(); } }; class B: public A { public: virtual void bFunction() { //stuff done here } };

How is calling a method different from calling a function?

A function has another property: all calls to a function with the same parameters, should return the same result. A method, on the other hand, is a function that is related to an object in an object-oriented language. It has one implicit parameter: the object being acted upon (and it's state).

What is the difference between class and method?

Definition. A class is a template for creating or instantiating objects within a program while a method is a function that exposes the behavior of an object. Thus, this is the main difference between class and method.


2 Answers

The short answer: You can't.

One of the things that is counterintuitive about generic types is that they are not related. A List<int>, for example, has no relationship whatsoever to a List<string>. They do not inherit from each other, and you can't cast one to the other.

You can declare a covariance relationship, which looks a lot like an inheritance relationship, but not between an int and a string as you have declared, since one is a value type and one is a reference type.

Your only alternative is to add another interface that they have in common, like this:

interface IDataMold
{
}

abstract class DataMold<T> : IDataMold
{
    public abstract T Result { get; }
}

Now you can store all of your molds in a List<IDataMold>. However, the interface has no properties, so you'd have a heckuva time getting anything out of it. You could add some properties, but they would not be type-specific, as IDataMold has no generic type parameter. But you could add a common property

interface IDataMold
{
    string ResultString { get; }
}

...and implement it:

abstract class DataMold<T>
{
    public abstract T Result { get; }
    public string ResultString => Result.ToString();
}

But if your only need is to display a string equivalent for each item, you can just override ToString() instead:

class TextMold : DataMold<string>
{
    public string Result => "ABC";
    public override string ToString() => Result.ToString();
}

Now you can do this:

List<IDataMold> molds = new List<IDataMold>();
molds.Add(new TextMold());
molds.Add(new NumberMold());

foreach (var mold in molds)
{
    Console.WriteLine(mold.ToString());
}
like image 99
John Wu Avatar answered Oct 22 '22 21:10

John Wu


You're looking for covariance. See the out keyword before T generic type parameter:

// Covariance and contravariance are only possible for
// interface and delegate generic params
public interface IDataMold<out T> 
{
   T Result { get; }
}

abstract class DataMold<T> : IDataMold<T>
{
  public abstract T Result { get; }
}

class StringMold : DataMold<string> {}

class Whatever {}

class WhateverMold : DataMold<Whatever> {}

Now inherit DataMold<T> and create a List<IDataMold<object>>:

var molds = new List<IDataMold<object>>();
molds.Add(new StringMold());
molds.Add(new WhateverMold());

BTW, you can't use covariance when it comes to cast IDataMold<int> to IDataMold<object>. Instead of repeating what's been already explained, please see this other Q&A: Why covariance and contravariance do not support value type

If you're really forced to implement IDataMold<int>, that list may be of type object:

var molds = new List<object>();
molds.add(new TextMold());
molds.add(new NumberMold());

And you may use Enumerable.OfType<T> to get subsets of molds:

var numberMolds = molds.OfType<IDataMold<int>>();
var textMolds = molds.OfType<IDataMold<string>>();

Also, you may create two lists:

var numberMolds = new List<IDataMold<int>>();
var textMolds = new List<IDataMold<string>>();

So you might mix them later as an IEnumerable<object> if you need to:

var allMolds = numberMolds.Cast<object>().Union(textMolds.Cast<object>());
like image 41
Matías Fidemraizer Avatar answered Oct 22 '22 21:10

Matías Fidemraizer