6

My class that passes itself to a method in another class. This external method (the last line in the code below) alters the object I’m passing accordingly, returns control, and continues on its merry way. But the last line ThisPaymentGateway.Process(ref this); says this at design time:

Cannot pass 'this' as a ref or out argument because it is read-only***

There are no read-only properties there. How can I resolve this?

using System;

namespace Something
{
  namespace Finance
  {
    namespace Donations
    {
      public class Electronic1 : Donation
      {
        private PaymentGateway ThisPaymentGateway { get; set; } = new PaymentGateway();

        public void RunController()
        {
          if (DonorType.ToUpper() == "IND" && (PaymentMethod.ToUpper() == "CREDIT CARD" || PaymentMethod.ToUpper() == "ECHECK"))
          {
            ThisPaymentGateway.Process(ref this);
          }
        }
      }
    }
  }
}
4444
  • 3,541
  • 10
  • 32
  • 43
TRH
  • 608
  • 1
  • 8
  • 24
  • how do you call `RunController()` ? – Tigran Sep 08 '16 at 21:54
  • Since this code won't compile I haven't written the calling code yet. – TRH Sep 08 '16 at 21:58
  • 1
    Does `Process` require the parameter to be passed by `ref`? The error message is because `this` cannot be re-assigned. If you need to pass using `ref` you'll need to create a new variable and pass that. – Lee Sep 08 '16 at 22:00
  • Are you coming from a C++ background? Because this is a concept I had difficulty with coming from C++. In C# all classes are passed by reference, so most likely what you are trying to do will work with just `ThisPaymentGateway.Process(this)` – TJ Rockefeller Sep 08 '16 at 22:03
  • Possible duplicate of [C# pass by value/ref?](http://stackoverflow.com/questions/436986/c-sharp-pass-by-value-ref) – nhouser9 Sep 08 '16 at 22:16

2 Answers2

8

Remove "ref" from the call and Process method signature

If you just want to "alter the object" and then return, you don't need to pass it by ref anyway. Classes are already reference types and therefore if you change the object properties in Process it will change them in the source object.

Scott Perham
  • 2,410
  • 1
  • 10
  • 20
  • Ok, I'm coming from a VB.NET background where each parameter in the method signature has to be defined either as ByVal or ByRef. Are you telling me that by reference (where the passed object itself is altered) is the default in C#? In I'm not complaining, but I have wonder why they even have that keyword. – TRH Sep 08 '16 at 22:11
  • In C#, classes are passed by ref and structs are passed byval by default - passing a reference to a struct allows you to change the original object content, passing a reference to a class allows you to change the actual reference... but not _this_ :P – Scott Perham Sep 08 '16 at 22:12
  • @THR just a note: it's exactly the same in VB.NET, parameters are by default ByVal (and it's optional) and you need to add ByRef only if you want to pass them by reference. – Adriano Repetti Sep 08 '16 at 22:15
  • 5
    @ScottPerham Note that reference types (classes) are also passed by value by default; it’s just that the value is a reference to the object (so it appears like “by ref”). Passing a reference type by reference (using `ref` or `out`) actually passes “the variable” and allows you to replace the object with something else (instead of just mutating the object). See also [Jon Skeet’s article on the topic](http://jonskeet.uk/csharp/parameters.html). – poke Sep 08 '16 at 22:20
  • @poke You are (of course) absolutely correct... however, for simplicities sake in a comment... it's passed by ref ;) – Scott Perham Sep 08 '16 at 22:22
  • @TRH `ByRef` is widely misunderstood and over used. A ref/ByRef parameter means instead of passing a value, a variable is passed and the function's code may change the value of the variable. "Normal" parameters are values derived from expressions. That the value of the parameter might be an object reference has nothing to do with it. – Tom Blodget Sep 08 '16 at 23:52
  • @ScottPerham If that were true, then the code wouldn't work even if you removed `ref`. It's essential that the parameter *not* be passed by reference here. Stating that it is still being passed by reference would be saying that the code shouldn't compile, for exactly the same reason that the code in the question doesn't compile. Your statement is completely contradictory and confusing. It is anything but simple, in addition to being a lie. – Servy Sep 09 '16 at 03:55
2

First of all do not use strings instead of enum, it may not be obvious but you're performing culture aware comparison and you're converting PaymentMethod to uppercase using current culture rules. What does it mean? Schoolbook example is Turkish locale: "ind".ToUpper() != "IND". If you need to compare two strings regardless case use String.Equals(PaymentMethod, "IND", StringComparer.InvariantCultureIgnoreCase) (here I assume, because one string is hard-coded that you want to perform comparison using invariant culture rules but also ordinal comparison may be appropriate).

In C# you do not need to declare namespace in that way, this syntax is valid:

namespace Something.Finance.Donations {
}

About your question: variables passed with ref may be changed. this (let's imagine it's a variable, just imagine...) obviously cannot be changed then you have that compiler error.

Read this:

static void ChangeAnimalWithRef(ref Animal animal) {
    animal = new Cat();
    Debug.Assert(animal.GetType() == typeof(Cat));
}

static void ChangeAnimalWithoutRef(Animal animal) {
    animal = new Cat();
    Debug.Assert(animal.GetType() == typeof(Cat));
}

void Test1() {
    Animal animal = new Dog();
    Debug.Assert(animal.GetType() == typeof(Dog));

    ChangeAnimalWith(ref animal);    
    Debug.Assert(animal.GetType() == typeof(Cat));
}

void Test2() {
    Animal animal = new Dog();
    Debug.Assert(animal.GetType() == typeof(Dog));

    ChangeAnimalWithoutRef(animal);    
    Debug.Assert(animal.GetType() == typeof(Dog));
}

Think them as a pointer to a variable instead of the value of that variable (in C# parameters are passed by value, you need ref and out to pass them by reference).

If you own ThisPaymentGateway.Process() method I'd bet you just do not need ref modifier and you're just used to C++ references with &. In C# things are different.

If, for any obscure reason, you do not own that method and its signature cannot be changed then you have two options:

1) Create a dummy variable:

Electronic1 dummy = this;
ThisPaymentGateway.Process(ref dummy);
Debug.Assert(Object.ReferenceEquals(dummy, this), "Ooops, what are we supposed to do?");

2) Call that function from outside the class (and change your logic little bit):

var obj = new Electronic1();
if (obj.CanRunController())
    obj.ThisPaymentGateway.Process(ref obj);

I strongly doubt you need any of them, it's just for completeness.

Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208